From d013ef103787ad2109af33d45da0331d04054c76 Mon Sep 17 00:00:00 2001 From: Balwinder Singh Date: Sun, 30 Jun 2024 09:43:46 -0700 Subject: [PATCH 01/31] First working version of the test: still commented out a lot of code --- ...mxx_mam_microphysics_process_interface.cpp | 12 +++-- .../eamxx/src/physics/register_physics.hpp | 2 +- .../src/share/io/scream_scorpio_interface.cpp | 4 +- .../eamxx/tests/single-process/CMakeLists.txt | 1 + .../mam/aero_microphys/CMakeLists.txt | 44 +++++++++++++++++++ .../mam/aero_microphys/input.yaml | 35 +++++++++++++++ .../mam/aero_microphys/output.yaml | 13 ++++++ 7 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt create mode 100644 components/eamxx/tests/single-process/mam/aero_microphys/input.yaml create mode 100644 components/eamxx/tests/single-process/mam/aero_microphys/output.yaml diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 090fdfcb731..ae908fc30a5 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -168,11 +168,9 @@ set_computed_group_impl(const FieldGroup& group) { "Error! MAM4 expects bundled fields for tracers.\n"); // how many aerosol/gas tracers do we expect? - int num_tracers = 2 * (mam_coupling::num_aero_modes() + - mam_coupling::num_aero_tracers()) + - mam_coupling::num_aero_gases(); + int num_tracers = mam_coupling::num_aero_modes() + mam_coupling::num_aero_tracers() + mam_coupling::num_aero_gases(); EKAT_REQUIRE_MSG(group.m_info->size() >= num_tracers, - "Error! MAM4 requires at least " << num_tracers << " aerosol tracers."); + "Error! MAM4 requires at least " << group.m_info->size()<<" "<::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); + /*const auto scan_policy = ekat::ExeSpaceUtils::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); const auto policy = ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); // preprocess input -- needs a scan for the calculation of atm height @@ -506,7 +504,7 @@ void MAMMicrophysics::run_impl(const double dt) { // postprocess output Kokkos::parallel_for("postprocess", policy, postprocess_); - Kokkos::fence(); + Kokkos::fence();*/ } void MAMMicrophysics::finalize_impl() { diff --git a/components/eamxx/src/physics/register_physics.hpp b/components/eamxx/src/physics/register_physics.hpp index d837e96311f..99956bb75f5 100644 --- a/components/eamxx/src/physics/register_physics.hpp +++ b/components/eamxx/src/physics/register_physics.hpp @@ -65,7 +65,7 @@ inline void register_physics () { proc_factory.register_product("Nudging",&create_atmosphere_process); #endif #ifdef EAMXX_HAS_MAM - proc_factory.register_product("mam4_micro",&create_atmosphere_process); + proc_factory.register_product("mam4_aero_microphys",&create_atmosphere_process); proc_factory.register_product("mam4_optics",&create_atmosphere_process); proc_factory.register_product("mam4_drydep",&create_atmosphere_process); proc_factory.register_product("mam4_aci",&create_atmosphere_process); diff --git a/components/eamxx/src/share/io/scream_scorpio_interface.cpp b/components/eamxx/src/share/io/scream_scorpio_interface.cpp index 8d2f64994dd..2075ef824ff 100644 --- a/components/eamxx/src/share/io/scream_scorpio_interface.cpp +++ b/components/eamxx/src/share/io/scream_scorpio_interface.cpp @@ -360,11 +360,11 @@ void finalize_subsystem () EKAT_REQUIRE_MSG (s.pio_sysid!=-1, "Error! PIO subsystem was already finalized.\n"); - for (auto& it : s.files) { + /*for (auto& it : s.files) { EKAT_REQUIRE_MSG (it.second.num_customers==0, "Error! ScorpioSession::finalize called, but a file is still in use elsewhere.\n" " - filename: " + it.first + "\n"); - } + }*/ s.files.clear(); for (auto& it : s.decomps) { diff --git a/components/eamxx/tests/single-process/CMakeLists.txt b/components/eamxx/tests/single-process/CMakeLists.txt index 40565d94bf8..2d0ae749c49 100644 --- a/components/eamxx/tests/single-process/CMakeLists.txt +++ b/components/eamxx/tests/single-process/CMakeLists.txt @@ -25,6 +25,7 @@ if (SCREAM_ENABLE_MAM) add_subdirectory(mam/wet_scav) add_subdirectory(mam/emissions) add_subdirectory(mam/constituent_fluxes) + add_subdirectory(mam/aero_microphys) endif() if (SCREAM_TEST_LEVEL GREATER_EQUAL SCREAM_TEST_LEVEL_EXPERIMENTAL) add_subdirectory(zm) diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt new file mode 100644 index 00000000000..323d932484b --- /dev/null +++ b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt @@ -0,0 +1,44 @@ +include (ScreamUtils) + +set (TEST_BASE_NAME mam4_aero_microphys_standalone) +set (FIXTURES_BASE_NAME ${TEST_BASE_NAME}_generate_output_nc_files) + +# Create the test +CreateADUnitTest(${TEST_BASE_NAME} + LABELS mam4_aero_microphys physics + LIBS mam + MPI_RANKS ${TEST_RANK_START} ${TEST_RANK_END} + FIXTURES_SETUP_INDIVIDUAL ${FIXTURES_BASE_NAME} +) + +# Set AD configurable options +set (ATM_TIME_STEP 1800) +SetVarDependingOnTestSize(NUM_STEPS 2 5 48) # 1h 2.5h 24h +set (RUN_T0 2021-10-12-45000) + +## Copy (and configure) yaml files needed by tests +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/input.yaml + ${CMAKE_CURRENT_BINARY_DIR}/input.yaml) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/output.yaml + ${CMAKE_CURRENT_BINARY_DIR}/output.yaml) + +# Ensure test input files are present in the data dir +GetInputFile(scream/init/${EAMxx_tests_IC_FILE_MAM4xx_72lev}) + +# Compare output files produced by npX tests, to ensure they are bfb +include (CompareNCFiles) + +CompareNCFilesFamilyMpi ( + TEST_BASE_NAME ${TEST_BASE_NAME} + FILE_META_NAME ${TEST_BASE_NAME}_output.INSTANT.nsteps_x1.npMPIRANKS.${RUN_T0}.nc + MPI_RANKS ${TEST_RANK_START} ${TEST_RANK_END} + LABELS mam4_aero_microphys physics + META_FIXTURES_REQUIRED ${FIXTURES_BASE_NAME}_npMPIRANKS_omp1 +) + +if (SCREAM_ENABLE_BASELINE_TESTS) + # Compare one of the output files with the baselines. + # Note: one is enough, since we already check that np1 is BFB with npX + set (OUT_FILE ${TEST_BASE_NAME}_output.INSTANT.nsteps_x1.np${TEST_RANK_END}.${RUN_T0}.nc) + CreateBaselineTest(${TEST_BASE_NAME} ${TEST_RANK_END} ${OUT_FILE} ${FIXTURES_BASE_NAME}) +endif() diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml new file mode 100644 index 00000000000..89426776ac6 --- /dev/null +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -0,0 +1,35 @@ +%YAML 1.1 +--- +driver_options: + atmosphere_dag_verbosity_level: 5 + +time_stepping: + time_step: ${ATM_TIME_STEP} + run_t0: ${RUN_T0} # YYYY-MM-DD-XXXXX + number_of_steps: ${NUM_STEPS} + +atmosphere_processes: + atm_procs_list: [mam4_aero_microphys] + +grids_manager: + Type: Mesh Free + geo_data_source: IC_FILE + grids_names: [Physics GLL] + Physics GLL: + type: point_grid + aliases: [Physics] + number_of_global_columns: 218 + number_of_vertical_levels: 72 + +initial_conditions: + # The name of the file containing the initial conditions for this test. + Filename: ${SCREAM_DATA_DIR}/init/${EAMxx_tests_IC_FILE_MAM4xx_72lev} + topography_filename: ${TOPO_DATA_DIR}/${EAMxx_tests_TOPO_FILE} + phis : 1.0 + pbl_height: 1.0 + #These should come from the input file + +# The parameters for I/O control +Scorpio: + output_yaml_files: ["output.yaml"] +... diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml new file mode 100644 index 00000000000..c5aa98e515a --- /dev/null +++ b/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml @@ -0,0 +1,13 @@ +%YAML 1.1 +--- +filename_prefix: mam4_aero_microphys_standalone_output +Averaging Type: Instant +Fields: + Physics: + Field Names: + - T_mid + +output_control: + Frequency: 1 + frequency_units: nsteps +... From 512b80b97d921897c0497b442336a4dab87841e9 Mon Sep 17 00:00:00 2001 From: Balwinder Singh Date: Mon, 1 Jul 2024 06:29:00 -0700 Subject: [PATCH 02/31] Adds p_int to fix atmosphere_for_column call for getting atm object --- ...mxx_mam_microphysics_process_interface.cpp | 67 ++++++++++++++----- ...mxx_mam_microphysics_process_interface.hpp | 8 ++- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index ae908fc30a5..6721928bf27 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -115,6 +115,8 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // layout for 3D (2d horiz X 1d vertical) variables FieldLayout scalar3d_layout_mid{ {COL, LEV}, {ncol_, nlev_} }; + // At interfaces + FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; // define fields needed in mam4xx @@ -122,13 +124,15 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ add_field("omega", scalar3d_layout_mid, Pa/s, grid_name); // vertical pressure velocity add_field("T_mid", scalar3d_layout_mid, K, grid_name); // Temperature add_field("p_mid", scalar3d_layout_mid, Pa, grid_name); // total pressure + // Total pressure [Pa] at interfaces + add_field("p_int", scalar3d_layout_int, Pa, grid_name); + add_field("qv", scalar3d_layout_mid, kg/kg, grid_name, "tracers"); // specific humidity + add_field("qi", scalar3d_layout_mid, kg/kg, grid_name, "tracers"); // ice wet mixing ratio + add_field("ni", scalar3d_layout_mid, n_unit, grid_name, "tracers"); // ice number mixing ratio add_field("pbl_height", scalar2d_layout_col, m, grid_name); // planetary boundary layer height add_field("pseudo_density", scalar3d_layout_mid, Pa, grid_name); // p_del, hydrostatic pressure add_field("phis", scalar2d_layout_col, m2/s2, grid_name); add_field("cldfrac_tot", scalar3d_layout_mid, nondim, grid_name); // cloud fraction - add_tracer("qv", grid_, kg/kg); // specific humidity - add_tracer("qi", grid_, kg/kg); // ice wet mixing ratio - add_tracer("ni", grid_, n_unit); // ice number mixing ratio // droplet activation can alter cloud liquid and number mixing ratios add_tracer("qc", grid_, kg/kg); // cloud liquid wet mixing ratio @@ -155,6 +159,37 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // Tracers group -- do we need this in addition to the tracers above? In any // case, this call should be idempotent, so it can't hurt. add_group("tracers", grid_name, 1, Bundling::Required); + // read linoz files +#if 1 + { + using view_1d_host = typename KT::view_1d::HostMirror; + using view_2d_host = typename KT::view_2d::HostMirror; + using strvec_t = std::vector; + std::map layouts_linoz; + // const auto& fname = m_params.get(table_name); + std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + ekat::ParameterList params_Linoz; + params_Linoz.set("Filename", linoz_file_name); + // make a list of host views + std::map host_views_Linoz; + + params_Linoz.set("Skip_Grid_Checks", true); + params_Linoz.set( + "Field Names", + {"o3_clim"}); + + view_2d_host o3_clim_host("o3_clim_host",ncol_, nlev_); + host_views_Linoz["o3_clim"] = + view_1d_host(o3_clim_host.data(), o3_clim_host.size()); + + layouts_linoz.emplace("o3_clim", scalar3d_layout_mid); + + AtmosphereInput Linoz_reader(params_Linoz, grid_, host_views_Linoz, + layouts_linoz); + Linoz_reader.read_variables(); + Linoz_reader.finalize(); + } +#endif } // this checks whether we have the tracers we expect @@ -190,7 +225,7 @@ void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { void MAMMicrophysics::initialize_impl(const RunType run_type) { - /*step_ = 0; + step_ = 0; // populate the wet and dry atmosphere states with views from fields and // the buffer @@ -203,6 +238,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { dry_atm_.T_mid = get_field_in("T_mid").get_view(); dry_atm_.p_mid = get_field_in("p_mid").get_view(); + dry_atm_.p_int = get_field_in("p_int").get_view(); dry_atm_.p_del = get_field_in("pseudo_density").get_view(); dry_atm_.cldfrac = get_field_in("cldfrac_tot").get_view(); // FIXME: tot or liq? dry_atm_.pblh = get_field_in("pbl_height").get_view(); @@ -245,9 +281,9 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { } // create our photolysis rate calculation table - photo_table_ = impl::read_photo_table(get_comm(), + /*photo_table_ = impl::read_photo_table(get_comm(), config_.photolysis.rsf_file, - config_.photolysis.xs_long_file); + config_.photolysis.xs_long_file);*/ // FIXME: read relevant land use data from drydep surface file @@ -273,7 +309,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { void MAMMicrophysics::run_impl(const double dt) { - /*const auto scan_policy = ekat::ExeSpaceUtils::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); + const auto scan_policy = ekat::ExeSpaceUtils::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); const auto policy = ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); // preprocess input -- needs a scan for the calculation of atm height @@ -310,13 +346,12 @@ void MAMMicrophysics::run_impl(const double dt) { mam4::mo_photo::PhotoTableData &photo_table = photo_table_; const int nlev = nlev_; const Config &config = config_; + const auto& step= step_; // FIXME: read relevant linoz climatology data from file(s) based on time // FIXME: read relevant chlorine loading data from file based on time // loop over atmosphere columns and compute aerosol microphyscs - auto some_step = step_; - Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const ThreadTeam& team) { const int icol = team.league_rank(); // column index @@ -324,11 +359,11 @@ void MAMMicrophysics::run_impl(const double dt) { // fetch column-specific atmosphere state data auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); - auto z_iface = ekat::subview(dry_atm.z_iface, icol); + /*auto z_iface = ekat::subview(dry_atm.z_iface, icol); Real phis = dry_atm.phis(icol); // set surface state data - haero::Surface sfc{}; + /*haero::Surface sfc{}; // fetch column-specific subviews into aerosol prognostics mam4::Prognostics progs = mam_coupling::interstitial_aerosols_for_column(dry_aero, icol); @@ -359,10 +394,10 @@ void MAMMicrophysics::run_impl(const double dt) { constexpr int extcnt = mam4::gas_chemistry::extcnt; view_2d extfrc; // FIXME: where to allocate? (nlev, extcnt) mam4::mo_setext::Forcing forcings[extcnt]; // FIXME: forcings seem to require file data - mam4::mo_setext::extfrc_set(forcings, extfrc); + mam4::mo_setext::extfrc_set(forcings, extfrc);*/ // compute aerosol microphysics on each vertical level within this column - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { + /*Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int gas_pcnst = mam_coupling::gas_pcnst(); @@ -448,7 +483,7 @@ void MAMMicrophysics::run_impl(const double dt) { impl::compute_water_content(progs, k, qv, temp, pmid, dgncur_a, dgncur_awet, wetdens, qaerwat); // do aerosol microphysics (gas-aerosol exchange, nucleation, coagulation) - impl::modal_aero_amicphys_intr(config.amicphys, step_, dt, t, pmid, pdel, + /*impl::modal_aero_amicphys_intr(config.amicphys, step, dt, t, pmid, pdel, zm, pblh, qv, cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a, dgncur_awet, @@ -499,12 +534,12 @@ void MAMMicrophysics::run_impl(const double dt) { // transfer updated prognostics from work arrays mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k); - }); + });*/ }); // postprocess output Kokkos::parallel_for("postprocess", policy, postprocess_); - Kokkos::fence();*/ + Kokkos::fence(); } void MAMMicrophysics::finalize_impl() { diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 5f5a44b846b..848a251bba6 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -10,6 +10,8 @@ #include #include #include +#include "share/io/scorpio_input.hpp" +#include "share/io/scream_scorpio_interface.hpp" #include @@ -140,12 +142,12 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { void operator()(const Kokkos::TeamPolicy::member_type& team) const { const int i = team.league_rank(); // column index - compute_vertical_layer_heights(team, dry_atm_, i); - team.team_barrier(); // allows kernels below to use layer heights - compute_updraft_velocities(team, wet_atm_, dry_atm_, i); compute_dry_mixing_ratios(team, wet_atm_, dry_atm_, i); compute_dry_mixing_ratios(team, wet_atm_, wet_aero_, dry_aero_, i); team.team_barrier(); + + compute_vertical_layer_heights(team, dry_atm_, i); + compute_updraft_velocities(team, wet_atm_, dry_atm_, i); } // operator() // number of horizontal columns and vertical levels From 525d86cde234702c408a3507256f83140bd8d949 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sat, 13 Jul 2024 09:45:10 -0600 Subject: [PATCH 03/31] linoz_reader - vertical interpolation linoz_reader - perform_time_interpolation linoz_reader - Setting linoz data structs. linoz_reader - performing time interpolation. --- ...mxx_mam_microphysics_process_interface.cpp | 107 +++- ...mxx_mam_microphysics_process_interface.hpp | 11 + .../src/physics/mam/impl/helper_micro.hpp | 594 ++++++++++++++++++ 3 files changed, 685 insertions(+), 27 deletions(-) create mode 100644 components/eamxx/src/physics/mam/impl/helper_micro.hpp diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 6721928bf27..2ef709c818f 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -13,6 +13,8 @@ #include "impl/compute_water_content.cpp" #include "impl/gas_phase_chemistry.cpp" + + namespace scream { @@ -160,34 +162,21 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // case, this call should be idempotent, so it can't hurt. add_group("tracers", grid_name, 1, Bundling::Required); // read linoz files -#if 1 +#if 1 { - using view_1d_host = typename KT::view_1d::HostMirror; - using view_2d_host = typename KT::view_2d::HostMirror; - using strvec_t = std::vector; - std::map layouts_linoz; - // const auto& fname = m_params.get(table_name); - std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; - ekat::ParameterList params_Linoz; - params_Linoz.set("Filename", linoz_file_name); - // make a list of host views - std::map host_views_Linoz; - - params_Linoz.set("Skip_Grid_Checks", true); - params_Linoz.set( - "Field Names", - {"o3_clim"}); - - view_2d_host o3_clim_host("o3_clim_host",ncol_, nlev_); - host_views_Linoz["o3_clim"] = - view_1d_host(o3_clim_host.data(), o3_clim_host.size()); - - layouts_linoz.emplace("o3_clim", scalar3d_layout_mid); - - AtmosphereInput Linoz_reader(params_Linoz, grid_, host_views_Linoz, - layouts_linoz); - Linoz_reader.read_variables(); - Linoz_reader.finalize(); + std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + // std::vector io_linoz_fields; + linoz_reader_ = create_linoz_data_reader(linoz_file_name,linoz_params_,m_comm); + linoz_reader_->read_variables(); + view_1d col_latitudes("col",ncol_); + Kokkos::deep_copy(col_latitudes, col_latitudes_); + view_2d o3_clim_org("o3_clim_test", nlev_,ncol_); + linoz_params_.views_horiz.push_back(o3_clim_org); + view_2d o3_clim_data("o3_clim_data", ncol_, linoz_params_.nlevs); + linoz_params_.views_horiz_transpose.push_back(o3_clim_data); + linoz_params_.col_latitudes = col_latitudes; + + perform_horizontal_interpolation(linoz_params_); } #endif } @@ -305,6 +294,44 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // (we'll probably need this later, but we'll just use ATMBufferManager for now) //const auto default_policy = ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); //workspace_mgr_.setup(buffer_.wsm_data, nlev_+1, 13+(n_wind_slots+n_trac_slots), default_policy); + + { + // climatology data for linear stratospheric chemistry + auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] + auto linoz_o3col_clim = buffer_.scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] + auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] + auto linoz_PmL_clim = buffer_.scratch[3]; // P minus L (climatology) [vmr/s] + auto linoz_dPmL_dO3 = buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] + auto linoz_dPmL_dT = buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] + auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] + auto linoz_cariolle_psc = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + + + // Load the first month into spa_end. + // Note: At the first time step, the data will be moved into spa_beg, + // and spa_end will be reloaded from file with the new month. + const int curr_month = timestamp().get_month()-1; // 0-based + std::cout << curr_month << " : curr_month \n"; + linoz_reader_->read_variables(curr_month); + perform_horizontal_interpolation(linoz_params_); + linoz_params_.views_vert.push_back(linoz_o3_clim); + linoz_params_.kupper = scream::mam_coupling::view_int_1d("kupper",ncol_); + linoz_params_.pin = view_2d("pin", ncol_,nlev_); + + perform_vertical_interpolation(linoz_params_, + dry_atm_.p_mid); + + LinozData_end_.init(ncol_,linoz_params_.nlevs); + LinozData_end_.allocate_data_views(); + + LinozData_start_.init(ncol_,linoz_params_.nlevs); + LinozData_start_.allocate_data_views(); + // LinozData_start_.deep_copy_data_views(LinozData_end_.data); + + LinozData_out_.init(ncol_,linoz_params_.nlevs); + // LinozData_out_.allocate_data_views(); + LinozData_end_.set_data_views(linoz_params_.views_horiz_transpose); + } } void MAMMicrophysics::run_impl(const double dt) { @@ -316,6 +343,8 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::parallel_for("preprocess", scan_policy, preprocess_); Kokkos::fence(); + + // reset internal WSM variables //workspace_mgr_.reset_internals(); @@ -340,6 +369,29 @@ void MAMMicrophysics::run_impl(const double dt) { // allocation perspective auto o3_col_dens = buffer_.scratch[8]; + { + /* Gather time and state information for interpolation */ + auto ts = timestamp()+dt; + /* Update the SPATimeState to reflect the current time, note the addition of dt */ + linoz_time_state_.t_now = ts.frac_of_year_in_days(); + /* Update time state and if the month has changed, update the data.*/ + update_linoz_timestate(ts, + linoz_time_state_, + linoz_reader_, + linoz_params_, + LinozData_start_, + LinozData_end_); + + perform_time_interpolation(linoz_time_state_, + LinozData_start_, + LinozData_end_, + LinozData_out_); + + perform_vertical_interpolation(linoz_params_, + dry_atm_.p_mid); + + } + const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; mam_coupling::AerosolState &dry_aero = dry_aero_; @@ -543,6 +595,7 @@ void MAMMicrophysics::run_impl(const double dt) { } void MAMMicrophysics::finalize_impl() { + // linoz_reader_->finalize(); } } // namespace scream diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 848a251bba6..8fa77791fd2 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -6,6 +6,7 @@ #include #include "impl/mam4_amicphys.cpp" // mam4xx top-level microphysics function(s) +#include "impl/helper_micro.hpp" #include #include @@ -13,6 +14,7 @@ #include "share/io/scorpio_input.hpp" #include "share/io/scream_scorpio_interface.hpp" + #include #ifndef KOKKOS_ENABLE_CUDA @@ -49,6 +51,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // a thread team dispatched to a single vertical column using ThreadTeam = mam4::ThreadTeam; + public: // Constructor @@ -225,6 +228,14 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // sets defaults for "namelist parameters" void set_defaults_(); + std::shared_ptr linoz_reader_; + mam_coupling::LinozReaderParams linoz_params_; + mam_coupling::view_1d col_latitudes_copy_; + mam_coupling::LinozTimeState linoz_time_state_; + mam_coupling::LinozData LinozData_start_; + mam_coupling::LinozData LinozData_end_; + mam_coupling::LinozData LinozData_out_; + }; // MAMMicrophysics } // namespace scream diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp new file mode 100644 index 00000000000..af4e63c7119 --- /dev/null +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -0,0 +1,594 @@ +#ifndef EAMXX_MAM_HELPER_MICRO +#define EAMXX_MAM_HELPER_MICRO + +#include "share/io/scorpio_input.hpp" +#include "share/io/scream_scorpio_interface.hpp" +#include "share/grid/point_grid.hpp" +#include +#include + +namespace scream::mam_coupling { + + using namespace ShortFieldTagsNames; + + // using npack equal to 1. + using LIV = ekat::LinInterp; + using ExeSpace = typename KT::ExeSpace; + using ESU = ekat::ExeSpaceUtils; + + struct LinozReaderParams { + // std::shared_ptr linoz_reader; + int nlevs{-1}; + int nlat{-1}; + std::vector io_fields; + std::vector views_horiz_transpose; + std::vector views_vert; + // work arrays + view_int_1d kupper; + std::vector views_horiz; + // + view_2d pin; + view_1d col_latitudes; + }; + + // Linoz structures to help manage all of the variables: + struct LinozTimeState { + // Whether the timestate has been initialized. + // The current month + int current_month = -1; + // Julian Date for the beginning of the month, as defined in + // /src/share/util/scream_time_stamp.hpp + // See this file for definition of Julian Date. + Real t_beg_month; + // Current simulation Julian Date + Real t_now; + // Number of days in the current month, cast as a Real + Real days_this_month; + }; // LinozTimeState + + struct LinozData { + + LinozData() = default; + LinozData(const int ncol, const int nlev) + { + init (ncol,nlev); + } + void init (const int ncol, const int nlev){ + ncol_=ncol; + nlev_=nlev; + } + int ncol_{-1}; + int nlev_{-1}; + int nvars_{1}; + view_2d data[1]; + + void allocate_data_views() + { + std::cout<< ncol_<<" ncol_ \n"; + std::cout<< nlev_<<" nlev_ \n"; + EKAT_REQUIRE_MSG (ncol_ != int(-1), + "Error! ncols has not been set. \n"); + EKAT_REQUIRE_MSG (nlev_ !=int(-1), + "Error! nlevs has not been set. \n"); + + for (int ivar = 0; ivar< nvars_; ++ivar) { + data[ivar] = view_2d("linoz_1",ncol_,nlev_); + } + } //allocate_data_views + + void set_data_views(std::vector& list_of_views) + { + for (int ivar = 0; ivar< nvars_; ++ivar) { + EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, + "Error! Insufficient memory size.\n"); + data[ivar] =list_of_views[ivar]; + } + } + void deep_copy_data_views(const view_2d in_data[]){ + + for (int ivar = 0; ivar< nvars_; ++ivar) { + EKAT_REQUIRE_MSG(data[ivar].data() != 0, + "Error! Insufficient memory size.\n"); + EKAT_REQUIRE_MSG(in_data[ivar].data() != 0, + "Error! Insufficient memory size.\n"); + Kokkos::deep_copy(data[ivar],in_data[ivar] ); + } + } + }; + + // define the different field layouts that will be used for this process + static std::shared_ptr + create_linoz_data_reader ( + const std::string& linoz_data_file, + // std::vector io_fields, + LinozReaderParams& linoz_params, + const ekat::Comm& comm) + { + + auto make_layout = [](const std::vector& extents, + const std::vector& names) + { + std::vector tags(extents.size(),CMP); + return FieldLayout(tags,extents,names); + }; + + // query the file for its resolution + scorpio::register_file(linoz_data_file,scorpio::Read); + const int nlevs_data = scorpio::get_dimlen(linoz_data_file,"lev"); + const int nlat_data = scorpio::get_dimlen(linoz_data_file,"lat"); + scorpio::release_file(linoz_data_file); + std::cout << "nlevs_data: " << nlevs_data << "\n"; + std::cout << "nlat_data: " << nlat_data << "\n"; + linoz_params.nlevs=nlevs_data; + linoz_params.nlat=nlat_data; + + + // create an IO grid, with that number of cols + // linoz files do not have number of cols, + // I will use nlat_data instead. + + std::string name="linoz_grid"; + const auto io_grid = std::make_shared(name,nlat_data,nlevs_data,comm); + + const auto nondim = ekat::units::Units::nondimensional(); + + auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, + {"lev","lat"}); + auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, + {"lat"}); + auto scalar1d_lev_layout_linoz = make_layout({nlevs_data}, + {"lev"}); + + Field o3_clim (FieldIdentifier("o3_clim", scalar2d_layout_linoz, nondim,io_grid->name())); + Field lat (FieldIdentifier("lat", scalar1d_lat_layout_linoz, nondim,io_grid->name())); + Field lev (FieldIdentifier("lev", scalar1d_lev_layout_linoz, nondim,io_grid->name())); + o3_clim.allocate_view(); + lat.allocate_view(); + lev.allocate_view(); + linoz_params.io_fields.push_back(lat); + linoz_params.io_fields.push_back(lev); + linoz_params.io_fields.push_back(o3_clim); + + return std::make_shared(linoz_data_file,io_grid,linoz_params.io_fields,true); + } + + + static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params) + { + // FIXME: get this inputs from eamxx interface. + const auto col_latitudes = linoz_params.col_latitudes; + const int ncol = col_latitudes.extent(0); + const int nlev =linoz_params.views_horiz[0].extent(0); + + + // We can ||ize over columns as well as over variables and bands + const int num_vars = linoz_params.nlevs; + LIV horiz_interp(num_vars, linoz_params.nlat, ncol); + const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol); + auto lat = linoz_params.io_fields[0].get_view(); + + Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + // Setup + horiz_interp.setup(team, lat, col_latitudes); + }); + Kokkos::fence(); + + // Now use the interpolation object in || over all variables. + const int outer_iters = linoz_params.nlevs; + const auto policy_interp = ESU::get_default_team_policy(outer_iters, ncol); + Kokkos::parallel_for("linoz_horizongal_interp_loop", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + const int kk = team.league_rank(); + const auto x1 = lat; + const auto x2 = col_latitudes; + auto var_org=linoz_params.io_fields[2].get_view(); + const auto y1 = ekat::subview(var_org,kk); + const auto y2 = ekat::subview(linoz_params.views_horiz[0],kk); + horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); + }); + Kokkos::fence(); + + + // FIXME: Does ekat or kokkos have a call to transpose a view? + const auto policy_transpose = ESU::get_default_team_policy(ncol*nlev,1); + Kokkos::parallel_for("transpose_horization_data", policy_transpose, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + const int icol = team.league_rank() / nlev; + const int ilev = team.league_rank() % nlev; + const auto input = linoz_params.views_horiz[0]; + const auto output = linoz_params.views_horiz_transpose[0]; + output(icol, ilev) = input(ilev, icol); + }); + Kokkos::fence(); + + } + + +void static +perform_vertical_interpolation_linear( + const LinozReaderParams& linoz_params, + const view_2d& p_tgt, + const int ncols, + const int nlevs) +{ + const int nlevs_src = linoz_params.nlevs; + const int nlevs_tgt = nlevs; + + LIV vert_interp(ncols,nlevs_src,nlevs_tgt); + + // We can ||ize over columns as well as over variables and bands + const int num_vars = 1; + const int num_vert_packs = nlevs_tgt; + const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); + + const auto lev = linoz_params.io_fields[1].get_view< Real*>(); + + // Setup the linear interpolation object + Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + const int icol = team.league_rank(); + // Setup + vert_interp.setup(team, lev, ekat::subview(p_tgt,icol)); + }); + Kokkos::fence(); + + // Now use the interpolation object in || over all variables. + const int outer_iters = ncols*num_vars; + const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); + Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + + const int icol = team.league_rank(); + const auto x1 = lev; + const auto x2 = ekat::subview(p_tgt,icol); + + const auto y1 = ekat::subview(linoz_params.views_horiz_transpose[0],icol); + const auto y2 = ekat::subview(linoz_params.views_vert[0],icol); + vert_interp.lin_interp(team, x1, x2, y1, y2, icol); + }); + Kokkos::fence(); +} + +// Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp +static void vert_interp(int ncol, + int levsiz, + int pver, + const view_2d& pin, + const const_view_2d& pmid, + const view_2d& datain, + const view_2d& dataout, + //work array + const view_int_1d& kupper + ) +{ + const int one = 1; + // Initialize index array + for (int i = 0; i < ncol; ++i) { + kupper(i)= one; + } // ncol + + for (int k = 0; k < pver; ++k) { + // Top level we need to start looking is the top level for the previous k for all column points + int kkstart = levsiz; + for (int i = 0; i < ncol; ++i) { + kkstart = haero::min(kkstart, kupper(i)); + } + + // Store level indices for interpolation + for (int kk = kkstart - 1; kk < levsiz - 1; ++kk) { + for (int i = 0; i < ncol; ++i) { + if (pin(i, kk) < pmid(i, k) && pmid(i, k) <= pin(i, kk + 1)) { + kupper(i) = kk; + }// end if + } // end for + } // end kk + // Interpolate or extrapolate... + for (int i = 0; i < ncol; ++i) { + if (pmid(i, k) < pin(i, 0)) { + dataout(i, k) = datain(i, 0) * pmid(i, k) / pin(i, 0); + } else if (pmid(i, k) > pin(i, levsiz - 1)) { + dataout(i, k) = datain(i, levsiz - 1); + } else { + Real dpu = pmid(i, k) - pin(i, kupper(i)); + Real dpl = pin(i, kupper(i) + 1) - pmid(i, k); + dataout(i, k) = (datain(i, kupper[i]) * dpl + datain(i, kupper(i) + 1) * dpu) / (dpl + dpu); + }// end if + } // end col + } // end k + +} // vert_interp + +static void perform_vertical_interpolation(const LinozReaderParams& linoz_params, + const const_view_2d& p_mid) +{ + const int ncol = p_mid.extent(0); + const int nlev = p_mid.extent(1); + + const auto kupper = linoz_params.kupper; + const auto levs = linoz_params.io_fields[1].get_view< Real*>(); + const auto pin = linoz_params.pin; + + for (int kk = 0; kk < linoz_params.nlevs; ++kk) + { + const auto pin_kk = Kokkos::subview(pin,Kokkos::ALL,kk); + Kokkos::deep_copy(pin_kk,levs(kk)); + }// i + Kokkos::fence(); + + const auto policy_interp = ESU::get_default_team_policy(1, 1); + Kokkos::parallel_for("vertical_interpolation_linoz", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + scream::mam_coupling::vert_interp(ncol, + linoz_params.nlevs, + nlev, + pin, + p_mid, + linoz_params.views_horiz_transpose[0], + linoz_params.views_vert[0], + //work array + kupper); + }); + Kokkos::fence(); + +}//perform_vertical_interpolation + +// This function is based on update_spa_timestate +void static update_linoz_timestate(const util::TimeStamp& ts, + LinozTimeState& time_state, + std::shared_ptr linoz_reader, + const LinozReaderParams& linoz_params, + LinozData& data_beg, + LinozData& data_end) +{ + // Now we check if we have to update the data that changes monthly + // NOTE: This means that SPA assumes monthly data to update. Not + // any other frequency. + const auto month = ts.get_month() - 1; // Make it 0-based + if (month != time_state.current_month) { + // Update the SPA time state information + time_state.current_month = month; + time_state.t_beg_month = util::TimeStamp({ts.get_year(),month+1,1}, {0,0,0}).frac_of_year_in_days(); + time_state.days_this_month = util::days_in_month(ts.get_year(),month+1); + + // // Copy spa_end'data into spa_beg'data, and read in the new spa_end + data_beg.deep_copy_data_views(data_end.data); + + // Update the SPA forcing data for this month and next month + // Start by copying next months data to this months data structure. + // NOTE: If the timestep is bigger than monthly this could cause the wrong values + // to be assigned. A timestep greater than a month is very unlikely so we + // will proceed. + int next_month = (time_state.current_month + 1) % 12; + linoz_reader->read_variables(next_month); + perform_horizontal_interpolation(linoz_params); + // + } // end if +} // update_linoz_timestate + +KOKKOS_INLINE_FUNCTION +static Real linear_interp(const Real& x0, const Real& x1, const Real& t) +{ + return (1 - t)*x0 + t*x1; +} // linear_interp + +KOKKOS_INLINE_FUNCTION +static view_1d get_var_column (const LinozData& data, + const int icol, + const int ivar) +{ + return ekat::subview(data.data[ivar],icol); +} // get_var_column +// This function is based on the SPA::perform_time_interpolation function. + static void perform_time_interpolation( + const LinozTimeState& time_state, + const LinozData& data_beg, + const LinozData& data_end, + const LinozData& data_out) +{ + // NOTE: we *assume* data_beg and data_end have the *same* hybrid v coords. + // IF this ever ceases to be the case, you can interp those too. + // Gather time stamp info + auto& t_now = time_state.t_now; + auto& t_beg = time_state.t_beg_month; + auto& delta_t = time_state.days_this_month; + + // We can ||ize over columns as well as over variables and bands + const int num_vars = data_beg.nvars_; + const int outer_iters = data_beg.ncol_*num_vars; + const int num_vert = data_beg.nlev_; + const auto policy = ESU::get_default_team_policy(outer_iters, num_vert); + + auto delta_t_fraction = (t_now-t_beg) / delta_t; + + EKAT_REQUIRE_MSG (delta_t_fraction>=0 && delta_t_fraction<=1, + "Error! Convex interpolation with coefficient out of [0,1].\n" + " t_now : " + std::to_string(t_now) + "\n" + " t_beg : " + std::to_string(t_beg) + "\n" + " delta_t: " + std::to_string(delta_t) + "\n"); + + Kokkos::parallel_for("linoz_time_interp_loop", policy, + KOKKOS_LAMBDA(const Team& team) { + + // The policy is over ncols*num_vars, so retrieve icol/ivar + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + // Get column of beg/end/out variable + auto var_beg = get_var_column (data_beg,icol,ivar); + auto var_end = get_var_column (data_end,icol,ivar); + auto var_out = get_var_column (data_out,icol,ivar); + + Kokkos::parallel_for (Kokkos::TeamVectorRange(team,num_vert), + [&] (const int& k) { + var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); + }); + }); + Kokkos::fence(); +} // perform_time_interpolation + + +} // namespace scream::mam_coupling +#endif //EAMXX_MAM_HELPER_MICRO + + // scream::mam_coupling::create_linoz_data_reader(linoz_file_name,linoz_params,m_comm); + + // auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, + // {"lev","lat"}); + + // auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, + // {"lat"}); + // ekat::ParameterList params_Linoz; + // params_Linoz.set("Filename", linoz_file_name); + // // make a list of host views + // std::map host_views_Linoz; + // params_Linoz.set("Skip_Grid_Checks", true); + // params_Linoz.set( + // "Field Names", + // {"o3_clim", + // "lat"}); + // view_2d_host o3_clim_host("o3_clim_host",nlevs_data, nlat_data); + // view_1d_host lat_host("lat_host", nlat_data); + // host_views_Linoz["o3_clim"] = + // view_1d_host(o3_clim_host.data(), o3_clim_host.size()); + // host_views_Linoz["lat"] = + // view_1d_host(lat_host.data(), lat_host.size()); + + // layouts_linoz.emplace("o3_clim", scalar2d_layout_linoz); + // layouts_linoz.emplace("lat", scalar1d_lat_layout_linoz); + + // linoz_reader_=std::make_shared(params_Linoz, grid_, host_views_Linoz, + // layouts_linoz); + + // linoz_reader_ = AtmosphereInput(params_Linoz, grid_, host_views_Linoz, + // layouts_linoz); + + // AtmosphereInput Linoz_reader(params_Linoz, grid_, host_views_Linoz, + // layouts_linoz); + // linoz_reader_ = scream::mam_coupling::create_linoz_data_reader(linoz_file_name); + // linoz_reader_->finalize(); +// #if 0 +// static void vertical_interpolation( LinozReaderParams& linoz_params, +// const view_2d& pmid) +// { +// const int nlev = pmid.extent(1); +// const int ncol = pmid.extent(0); +// const int num_vars = linoz_params.nlevs; +// LIV horiz_interp(num_vars, nlev, ncol); +// const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol); + +// auto lev = linoz_params.io_fields[1].get_view(); +// // We can ||ize over columns as well as over variables and bands +// LIV ver_interp(num_vars, linoz_params.nlevs, nlev); +// const auto policy_setup = ESU::get_default_team_policy(num_vars, nlev); + + + +// } + +// static std::shared_ptr +// create_linoz_data_reader ( +// const std::string& linoz_data_file) +// { + +// using view_1d_host = typename KT::view_1d::HostMirror; +// using view_2d_host = typename KT::view_2d::HostMirror; +// using strvec_t = std::vector; + + +// std::map layouts_linoz; +// // const auto& fname = m_params.get(table_name); +// scorpio::register_file(linoz_data_file,scorpio::Read); +// const int nlevs_data = scorpio::get_dimlen(linoz_data_file,"lev"); +// const int nlat_data = scorpio::get_dimlen(linoz_data_file,"lat"); +// scorpio::release_file(linoz_data_file); +// std::cout << "nlevs_data" << nlevs_data << "\n"; +// std::cout << "nlat_data" << nlat_data << "\n"; + +// auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, +// {"lev","lat"}); + +// auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, +// {"lat"}); +// ekat::ParameterList params_Linoz; +// params_Linoz.set("Filename", linoz_data_file); +// // make a list of host views +// std::map host_views_Linoz; +// params_Linoz.set("Skip_Grid_Checks", true); +// params_Linoz.set( +// "Field Names", +// {"o3_clim", +// "lat"}); +// view_2d_host o3_clim_host("o3_clim_host",nlevs_data, nlat_data); +// view_1d_host lat_host("lat_host", nlat_data); +// host_views_Linoz["o3_clim"] = +// view_1d_host(o3_clim_host.data(), o3_clim_host.size()); +// host_views_Linoz["lat"] = +// view_1d_host(lat_host.data(), lat_host.size()); + +// layouts_linoz.emplace("o3_clim", scalar2d_layout_linoz); +// layouts_linoz.emplace("lat", scalar1d_lat_layout_linoz); + +// return std::make_shared(params_Linoz, grid_, host_views_Linoz, +// layouts_linoz); +// } +// #endif + +// #if 0 +// std::cout << "o3_clim: " << o3_clim_host(0,0) << "\n"; +// std::cout << "lat: " << lat_host(0) << "\n"; + +// std::cout << "col_latitudes_.extents(0): " << col_latitudes_.extent(0) << "\n"; +// std::cout << "ncol_: " << ncol_ << "\n"; + + + +// using LIV = ekat::LinInterp; +// using ExeSpace = typename KT::ExeSpace; +// using ESU = ekat::ExeSpaceUtils; + +// // We can ||ize over columns as well as over variables and bands +// const int num_vars = nlevs_data; +// LIV horiz_interp(num_vars, nlat_data, ncol_); +// const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol_); +// // Setup the linear interpolation object +// // auto& col_latitudes= col_latitudes_; +// col_latitudes_copy_ = view_1d("col",ncol_); +// Kokkos::deep_copy(col_latitudes_copy_, col_latitudes_); + +// view_1d lat("lat",nlat_data ); +// Kokkos::deep_copy(lat, lat_host); + +// Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, +// KOKKOS_LAMBDA(typename LIV::MemberType const& team) { +// // Setup +// horiz_interp.setup(team, lat, col_latitudes); +// }); +// Kokkos::fence(); + +// // Now use the interpolation object in || over all variables. +// const int outer_iters = nlevs_data; +// const auto policy_interp = ESU::get_default_team_policy(outer_iters, ncol_); +// Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, +// KOKKOS_LAMBDA(typename LIV::MemberType const& team) { +// const int kk = team.league_rank(); +// const auto x1 = lat; +// const auto x2 = col_latitudes; +// const auto y1 = ekat::subview(o3_clim_host,kk); +// const auto y2 = ekat::subview(o3_clim_test,kk); +// horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); +// }); +// Kokkos::fence(); +// std::cout << "o3_clim_host \n"; +// for (int i = 0; i < nlat_data; ++i) +// { +// std::cout < Date: Sun, 14 Jul 2024 12:43:10 -0600 Subject: [PATCH 04/31] linoz_reader - Using LinozData in vertical interpolation function. linoz_reader- Removing deep_copy_data_views from linoz data struct. linoz_reader - Getting constat from linoz data structs. --- ...mxx_mam_microphysics_process_interface.cpp | 32 ++++++++------ ...mxx_mam_microphysics_process_interface.hpp | 1 + .../src/physics/mam/impl/helper_micro.hpp | 42 +++++++++---------- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 2ef709c818f..2599c5d079d 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -306,6 +306,20 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] auto linoz_cariolle_psc = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + LinozData_end_.init(ncol_,linoz_params_.nlevs); + LinozData_end_.allocate_data_views(); + + LinozData_start_.init(ncol_,linoz_params_.nlevs); + LinozData_start_.allocate_data_views(); + + LinozData_out_.init(ncol_,linoz_params_.nlevs); + LinozData_out_.allocate_data_views(); + // LinozData_end_.set_data_views(linoz_params_.views_horiz_transpose); + + interpolated_Linoz_data_.init(ncol_,nlev_); + std::vector list_linoz_views; + list_linoz_views.push_back(linoz_o3_clim); + interpolated_Linoz_data_.set_data_views(list_linoz_views); // Load the first month into spa_end. // Note: At the first time step, the data will be moved into spa_beg, @@ -319,18 +333,10 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { linoz_params_.pin = view_2d("pin", ncol_,nlev_); perform_vertical_interpolation(linoz_params_, - dry_atm_.p_mid); + dry_atm_.p_mid, + LinozData_end_, + interpolated_Linoz_data_); - LinozData_end_.init(ncol_,linoz_params_.nlevs); - LinozData_end_.allocate_data_views(); - - LinozData_start_.init(ncol_,linoz_params_.nlevs); - LinozData_start_.allocate_data_views(); - // LinozData_start_.deep_copy_data_views(LinozData_end_.data); - - LinozData_out_.init(ncol_,linoz_params_.nlevs); - // LinozData_out_.allocate_data_views(); - LinozData_end_.set_data_views(linoz_params_.views_horiz_transpose); } } @@ -388,7 +394,9 @@ void MAMMicrophysics::run_impl(const double dt) { LinozData_out_); perform_vertical_interpolation(linoz_params_, - dry_atm_.p_mid); + dry_atm_.p_mid, + LinozData_out_, + interpolated_Linoz_data_); } diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 8fa77791fd2..563af4401b6 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -235,6 +235,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::LinozData LinozData_start_; mam_coupling::LinozData LinozData_end_; mam_coupling::LinozData LinozData_out_; + mam_coupling::LinozData interpolated_Linoz_data_; }; // MAMMicrophysics diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index af4e63c7119..e65f140be44 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -84,16 +84,6 @@ namespace scream::mam_coupling { data[ivar] =list_of_views[ivar]; } } - void deep_copy_data_views(const view_2d in_data[]){ - - for (int ivar = 0; ivar< nvars_; ++ivar) { - EKAT_REQUIRE_MSG(data[ivar].data() != 0, - "Error! Insufficient memory size.\n"); - EKAT_REQUIRE_MSG(in_data[ivar].data() != 0, - "Error! Insufficient memory size.\n"); - Kokkos::deep_copy(data[ivar],in_data[ivar] ); - } - } }; // define the different field layouts that will be used for this process @@ -160,7 +150,6 @@ namespace scream::mam_coupling { const int ncol = col_latitudes.extent(0); const int nlev =linoz_params.views_horiz[0].extent(0); - // We can ||ize over columns as well as over variables and bands const int num_vars = linoz_params.nlevs; LIV horiz_interp(num_vars, linoz_params.nlat, ncol); @@ -300,32 +289,40 @@ static void vert_interp(int ncol, } // vert_interp static void perform_vertical_interpolation(const LinozReaderParams& linoz_params, - const const_view_2d& p_mid) + const const_view_2d& p_mid, + LinozData& non_interpolated_linoz, + LinozData& interpolated_linoz) { - const int ncol = p_mid.extent(0); - const int nlev = p_mid.extent(1); + const int ncol = interpolated_linoz.ncol_; + const int nlev = interpolated_linoz.nlev_; + + const int nvars = non_interpolated_linoz.nvars_; + const int nlevs_linoz = non_interpolated_linoz.nlev_; const auto kupper = linoz_params.kupper; const auto levs = linoz_params.io_fields[1].get_view< Real*>(); const auto pin = linoz_params.pin; - - for (int kk = 0; kk < linoz_params.nlevs; ++kk) + // FIXME: we do not need to update pin every time iteration. + for (int kk = 0; kk < nlevs_linoz; ++kk) { const auto pin_kk = Kokkos::subview(pin,Kokkos::ALL,kk); Kokkos::deep_copy(pin_kk,levs(kk)); }// i Kokkos::fence(); - const auto policy_interp = ESU::get_default_team_policy(1, 1); + const auto policy_interp = ESU::get_default_team_policy(nvars, 1); Kokkos::parallel_for("vertical_interpolation_linoz", policy_interp, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + const int ivar = team.league_rank(); + const auto var_non_inter = non_interpolated_linoz.data[ivar]; + const auto var_inter = interpolated_linoz.data[ivar]; scream::mam_coupling::vert_interp(ncol, - linoz_params.nlevs, + nlevs_linoz, nlev, pin, p_mid, - linoz_params.views_horiz_transpose[0], - linoz_params.views_vert[0], + var_non_inter, + var_inter, //work array kupper); }); @@ -352,7 +349,10 @@ void static update_linoz_timestate(const util::TimeStamp& ts, time_state.days_this_month = util::days_in_month(ts.get_year(),month+1); // // Copy spa_end'data into spa_beg'data, and read in the new spa_end - data_beg.deep_copy_data_views(data_end.data); + for (int ivar = 0; ivar < data_beg.nvars_; ++ivar) + { + Kokkos::deep_copy(data_beg.data[ivar],data_end.data[ivar]); + } // Update the SPA forcing data for this month and next month // Start by copying next months data to this months data structure. From 90b98543819c00d62829af8c97b9b0c9ded29102 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sun, 14 Jul 2024 14:20:06 -0600 Subject: [PATCH 05/31] linoz_reader - 1. Using LinozData in perform_horizontal_interpolation. 2. Removing views_horiz_transpose from LinozReaderParams. linoz_reader- Consider using arrays of views instead of std::vector for views_horiz. --- ...mxx_mam_microphysics_process_interface.cpp | 10 +- .../src/physics/mam/impl/helper_micro.hpp | 141 +++++++++--------- 2 files changed, 74 insertions(+), 77 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 2599c5d079d..0b1722dec6f 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -171,12 +171,10 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ view_1d col_latitudes("col",ncol_); Kokkos::deep_copy(col_latitudes, col_latitudes_); view_2d o3_clim_org("o3_clim_test", nlev_,ncol_); - linoz_params_.views_horiz.push_back(o3_clim_org); - view_2d o3_clim_data("o3_clim_data", ncol_, linoz_params_.nlevs); - linoz_params_.views_horiz_transpose.push_back(o3_clim_data); + linoz_params_.views_horiz[0]=o3_clim_org; linoz_params_.col_latitudes = col_latitudes; - perform_horizontal_interpolation(linoz_params_); + // perform_horizontal_interpolation(linoz_params_); } #endif } @@ -314,7 +312,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { LinozData_out_.init(ncol_,linoz_params_.nlevs); LinozData_out_.allocate_data_views(); - // LinozData_end_.set_data_views(linoz_params_.views_horiz_transpose); interpolated_Linoz_data_.init(ncol_,nlev_); std::vector list_linoz_views; @@ -327,8 +324,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { const int curr_month = timestamp().get_month()-1; // 0-based std::cout << curr_month << " : curr_month \n"; linoz_reader_->read_variables(curr_month); - perform_horizontal_interpolation(linoz_params_); - linoz_params_.views_vert.push_back(linoz_o3_clim); + perform_horizontal_interpolation(linoz_params_,LinozData_end_); linoz_params_.kupper = scream::mam_coupling::view_int_1d("kupper",ncol_); linoz_params_.pin = view_2d("pin", ncol_,nlev_); diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index e65f140be44..7956b76802b 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -17,15 +17,13 @@ namespace scream::mam_coupling { using ESU = ekat::ExeSpaceUtils; struct LinozReaderParams { - // std::shared_ptr linoz_reader; int nlevs{-1}; int nlat{-1}; std::vector io_fields; - std::vector views_horiz_transpose; - std::vector views_vert; + // work arrays view_int_1d kupper; - std::vector views_horiz; + view_2d views_horiz[1]; // view_2d pin; view_1d col_latitudes; @@ -64,8 +62,6 @@ namespace scream::mam_coupling { void allocate_data_views() { - std::cout<< ncol_<<" ncol_ \n"; - std::cout<< nlev_<<" nlev_ \n"; EKAT_REQUIRE_MSG (ncol_ != int(-1), "Error! ncols has not been set. \n"); EKAT_REQUIRE_MSG (nlev_ !=int(-1), @@ -143,17 +139,18 @@ namespace scream::mam_coupling { } - static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params) + static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, + LinozData& linoz_data_out) { // FIXME: get this inputs from eamxx interface. const auto col_latitudes = linoz_params.col_latitudes; - const int ncol = col_latitudes.extent(0); - const int nlev =linoz_params.views_horiz[0].extent(0); + const int ncol = linoz_data_out.ncol_; + const int linoz_data_nlev = linoz_data_out.nlev_; + const int nvars = linoz_data_out.nvars_; // We can ||ize over columns as well as over variables and bands - const int num_vars = linoz_params.nlevs; - LIV horiz_interp(num_vars, linoz_params.nlat, ncol); - const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol); + LIV horiz_interp(linoz_data_nlev, linoz_params.nlat, ncol); + const auto policy_setup = ESU::get_default_team_policy(1, ncol); auto lat = linoz_params.io_fields[0].get_view(); Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, @@ -171,74 +168,31 @@ namespace scream::mam_coupling { const int kk = team.league_rank(); const auto x1 = lat; const auto x2 = col_latitudes; - auto var_org=linoz_params.io_fields[2].get_view(); - const auto y1 = ekat::subview(var_org,kk); - const auto y2 = ekat::subview(linoz_params.views_horiz[0],kk); - horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); + for (int ivar = 0; ivar < nvars; ++ivar) { + auto var_org = linoz_params.io_fields[ivar+2].get_view(); + const auto y1 = ekat::subview(var_org,kk); + const auto y2 = ekat::subview(linoz_params.views_horiz[ivar],kk); + horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); + } }); Kokkos::fence(); // FIXME: Does ekat or kokkos have a call to transpose a view? - const auto policy_transpose = ESU::get_default_team_policy(ncol*nlev,1); + const auto policy_transpose = ESU::get_default_team_policy(ncol*linoz_data_nlev,1); Kokkos::parallel_for("transpose_horization_data", policy_transpose, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int icol = team.league_rank() / nlev; - const int ilev = team.league_rank() % nlev; - const auto input = linoz_params.views_horiz[0]; - const auto output = linoz_params.views_horiz_transpose[0]; - output(icol, ilev) = input(ilev, icol); + const int icol = team.league_rank() / linoz_data_nlev; + const int ilev = team.league_rank() % linoz_data_nlev; + for (int ivar = 0; ivar < nvars; ++ivar) { + const auto input = linoz_params.views_horiz[ivar]; + const auto output = linoz_data_out.data[ivar]; + output(icol, ilev) = input(ilev, icol); + }// ivar }); Kokkos::fence(); } - - -void static -perform_vertical_interpolation_linear( - const LinozReaderParams& linoz_params, - const view_2d& p_tgt, - const int ncols, - const int nlevs) -{ - const int nlevs_src = linoz_params.nlevs; - const int nlevs_tgt = nlevs; - - LIV vert_interp(ncols,nlevs_src,nlevs_tgt); - - // We can ||ize over columns as well as over variables and bands - const int num_vars = 1; - const int num_vert_packs = nlevs_tgt; - const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); - - const auto lev = linoz_params.io_fields[1].get_view< Real*>(); - - // Setup the linear interpolation object - Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int icol = team.league_rank(); - // Setup - vert_interp.setup(team, lev, ekat::subview(p_tgt,icol)); - }); - Kokkos::fence(); - - // Now use the interpolation object in || over all variables. - const int outer_iters = ncols*num_vars; - const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); - Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - - const int icol = team.league_rank(); - const auto x1 = lev; - const auto x2 = ekat::subview(p_tgt,icol); - - const auto y1 = ekat::subview(linoz_params.views_horiz_transpose[0],icol); - const auto y2 = ekat::subview(linoz_params.views_vert[0],icol); - vert_interp.lin_interp(team, x1, x2, y1, y2, icol); - }); - Kokkos::fence(); -} - // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp static void vert_interp(int ncol, int levsiz, @@ -361,7 +315,7 @@ void static update_linoz_timestate(const util::TimeStamp& ts, // will proceed. int next_month = (time_state.current_month + 1) % 12; linoz_reader->read_variables(next_month); - perform_horizontal_interpolation(linoz_params); + perform_horizontal_interpolation(linoz_params, data_end); // } // end if } // update_linoz_timestate @@ -427,6 +381,53 @@ static view_1d get_var_column (const LinozData& data, Kokkos::fence(); } // perform_time_interpolation +#if 0 +void static +perform_vertical_interpolation_linear( + const LinozReaderParams& linoz_params, + const view_2d& p_tgt, + const int ncols, + const int nlevs) +{ + const int nlevs_src = linoz_params.nlevs; + const int nlevs_tgt = nlevs; + + LIV vert_interp(ncols,nlevs_src,nlevs_tgt); + + // We can ||ize over columns as well as over variables and bands + const int num_vars = 1; + const int num_vert_packs = nlevs_tgt; + const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); + + const auto lev = linoz_params.io_fields[1].get_view< Real*>(); + + // Setup the linear interpolation object + Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + const int icol = team.league_rank(); + // Setup + vert_interp.setup(team, lev, ekat::subview(p_tgt,icol)); + }); + Kokkos::fence(); + + // Now use the interpolation object in || over all variables. + const int outer_iters = ncols*num_vars; + const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); + Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + + const int icol = team.league_rank(); + const auto x1 = lev; + const auto x2 = ekat::subview(p_tgt,icol); + + const auto y1 = ekat::subview(linoz_params.views_horiz_transpose[0],icol); + const auto y2 = ekat::subview(linoz_params.views_vert[0],icol); + vert_interp.lin_interp(team, x1, x2, y1, y2, icol); + }); + Kokkos::fence(); +} +#endif + } // namespace scream::mam_coupling #endif //EAMXX_MAM_HELPER_MICRO From dad50f76dcdfde87eaa6af7107278639e19d5ff9 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sun, 14 Jul 2024 15:07:13 -0600 Subject: [PATCH 06/31] linoz_reader - Adding arrays of views for variables, latitudes, and levels (levs) instead of using Field. --- .../src/physics/mam/impl/helper_micro.hpp | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 7956b76802b..c146628e466 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -19,11 +19,19 @@ namespace scream::mam_coupling { struct LinozReaderParams { int nlevs{-1}; int nlat{-1}; - std::vector io_fields; + // latitude array in linoz data. + view_1d latitudes; + // hybrid level pressure at interfaces (1000*(A+B)) + view_1d levs; + + // non_interpolated data from linoz files. + view_2d data_orig[1]; + + // data arrays after horizontal interpolation. + view_2d views_horiz[1]; // work arrays view_int_1d kupper; - view_2d views_horiz[1]; // view_2d pin; view_1d col_latitudes; @@ -86,7 +94,6 @@ namespace scream::mam_coupling { static std::shared_ptr create_linoz_data_reader ( const std::string& linoz_data_file, - // std::vector io_fields, LinozReaderParams& linoz_params, const ekat::Comm& comm) { @@ -103,12 +110,9 @@ namespace scream::mam_coupling { const int nlevs_data = scorpio::get_dimlen(linoz_data_file,"lev"); const int nlat_data = scorpio::get_dimlen(linoz_data_file,"lat"); scorpio::release_file(linoz_data_file); - std::cout << "nlevs_data: " << nlevs_data << "\n"; - std::cout << "nlat_data: " << nlat_data << "\n"; linoz_params.nlevs=nlevs_data; linoz_params.nlat=nlat_data; - // create an IO grid, with that number of cols // linoz files do not have number of cols, // I will use nlat_data instead. @@ -125,17 +129,29 @@ namespace scream::mam_coupling { auto scalar1d_lev_layout_linoz = make_layout({nlevs_data}, {"lev"}); - Field o3_clim (FieldIdentifier("o3_clim", scalar2d_layout_linoz, nondim,io_grid->name())); + const int nvars=1; + Field lat (FieldIdentifier("lat", scalar1d_lat_layout_linoz, nondim,io_grid->name())); Field lev (FieldIdentifier("lev", scalar1d_lev_layout_linoz, nondim,io_grid->name())); + + Field o3_clim (FieldIdentifier("o3_clim", scalar2d_layout_linoz, nondim,io_grid->name())); + o3_clim.allocate_view(); lat.allocate_view(); lev.allocate_view(); - linoz_params.io_fields.push_back(lat); - linoz_params.io_fields.push_back(lev); - linoz_params.io_fields.push_back(o3_clim); - return std::make_shared(linoz_data_file,io_grid,linoz_params.io_fields,true); + std::vector io_fields; + io_fields.push_back(lat); + io_fields.push_back(lev); + io_fields.push_back(o3_clim); + + linoz_params.latitudes=io_fields[0].get_view(); + linoz_params.levs = io_fields[1].get_view< Real*>(); + + for (int ivar = 0; ivar < nvars; ++ivar) { + linoz_params.data_orig[ivar] = io_fields[ivar+2].get_view(); + } + return std::make_shared(linoz_data_file, io_grid,io_fields,true); } @@ -151,7 +167,7 @@ namespace scream::mam_coupling { // We can ||ize over columns as well as over variables and bands LIV horiz_interp(linoz_data_nlev, linoz_params.nlat, ncol); const auto policy_setup = ESU::get_default_team_policy(1, ncol); - auto lat = linoz_params.io_fields[0].get_view(); + auto lat = linoz_params.latitudes; Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { @@ -169,7 +185,7 @@ namespace scream::mam_coupling { const auto x1 = lat; const auto x2 = col_latitudes; for (int ivar = 0; ivar < nvars; ++ivar) { - auto var_org = linoz_params.io_fields[ivar+2].get_view(); + const auto var_org = linoz_params.data_orig[ivar]; const auto y1 = ekat::subview(var_org,kk); const auto y2 = ekat::subview(linoz_params.views_horiz[ivar],kk); horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); @@ -254,7 +270,7 @@ static void perform_vertical_interpolation(const LinozReaderParams& linoz_params const int nlevs_linoz = non_interpolated_linoz.nlev_; const auto kupper = linoz_params.kupper; - const auto levs = linoz_params.io_fields[1].get_view< Real*>(); + const auto levs = linoz_params.levs; const auto pin = linoz_params.pin; // FIXME: we do not need to update pin every time iteration. for (int kk = 0; kk < nlevs_linoz; ++kk) From 72cfdcb08c992166c5a6657fcb1a530ab7f5dc42 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sun, 14 Jul 2024 15:38:57 -0600 Subject: [PATCH 07/31] linoz_reader - Allocate views of LinozReaderParams in create_linoz_data_reader. linoz_data - NVARS_LINOZ Linoz_reader - Adding o3col_clim. --- ...mxx_mam_microphysics_process_interface.cpp | 30 ++++--- .../src/physics/mam/impl/helper_micro.hpp | 78 +++++++++++++++---- 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 0b1722dec6f..b513ae87173 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -165,16 +165,8 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ #if 1 { std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; - // std::vector io_linoz_fields; - linoz_reader_ = create_linoz_data_reader(linoz_file_name,linoz_params_,m_comm); - linoz_reader_->read_variables(); - view_1d col_latitudes("col",ncol_); - Kokkos::deep_copy(col_latitudes, col_latitudes_); - view_2d o3_clim_org("o3_clim_test", nlev_,ncol_); - linoz_params_.views_horiz[0]=o3_clim_org; - linoz_params_.col_latitudes = col_latitudes; - - // perform_horizontal_interpolation(linoz_params_); + linoz_reader_ = create_linoz_data_reader(linoz_file_name, + linoz_params_, ncol_, col_latitudes_, m_comm); } #endif } @@ -314,19 +306,23 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { LinozData_out_.allocate_data_views(); interpolated_Linoz_data_.init(ncol_,nlev_); - std::vector list_linoz_views; - list_linoz_views.push_back(linoz_o3_clim); - interpolated_Linoz_data_.set_data_views(list_linoz_views); + + interpolated_Linoz_data_.set_data_views(linoz_o3_clim, + linoz_o3col_clim, + linoz_t_clim, + linoz_PmL_clim, + linoz_dPmL_dO3, + linoz_dPmL_dT, + linoz_dPmL_dO3col); + // , + // linoz_cariolle_psc); // Load the first month into spa_end. // Note: At the first time step, the data will be moved into spa_beg, // and spa_end will be reloaded from file with the new month. const int curr_month = timestamp().get_month()-1; // 0-based - std::cout << curr_month << " : curr_month \n"; linoz_reader_->read_variables(curr_month); - perform_horizontal_interpolation(linoz_params_,LinozData_end_); - linoz_params_.kupper = scream::mam_coupling::view_int_1d("kupper",ncol_); - linoz_params_.pin = view_2d("pin", ncol_,nlev_); + perform_horizontal_interpolation(linoz_params_, LinozData_end_); perform_vertical_interpolation(linoz_params_, dry_atm_.p_mid, diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index c146628e466..58698e56542 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -16,6 +16,14 @@ namespace scream::mam_coupling { using ExeSpace = typename KT::ExeSpace; using ESU = ekat::ExeSpaceUtils; + constexpr int NVARS_LINOZ=7; + const std::vector + linoz_var_names={"o3_clim", "o3col_clim", + "t_clim", "PmL_clim", "dPmL_dO3", + "dPmL_dT", "dPmL_dO3col"}; + // ,"cariolle_psc"}; + + struct LinozReaderParams { int nlevs{-1}; int nlat{-1}; @@ -25,10 +33,10 @@ namespace scream::mam_coupling { view_1d levs; // non_interpolated data from linoz files. - view_2d data_orig[1]; + view_2d data_orig[NVARS_LINOZ]; // data arrays after horizontal interpolation. - view_2d views_horiz[1]; + view_2d data_horiz[NVARS_LINOZ]; // work arrays view_int_1d kupper; @@ -65,8 +73,8 @@ namespace scream::mam_coupling { } int ncol_{-1}; int nlev_{-1}; - int nvars_{1}; - view_2d data[1]; + int nvars_{NVARS_LINOZ}; + view_2d data[NVARS_LINOZ]; void allocate_data_views() { @@ -88,6 +96,27 @@ namespace scream::mam_coupling { data[ivar] =list_of_views[ivar]; } } + + void set_data_views(const view_2d& linoz_o3_clim, + const view_2d& linoz_o3col_clim, + const view_2d& linoz_t_clim, + const view_2d& linoz_PmL_clim, + const view_2d& linoz_dPmL_dO3, + const view_2d& linoz_dPmL_dT, + const view_2d& linoz_dPmL_dO3col) + // , + // const view_2d& linoz_cariolle_psc) + { + data[0] = linoz_o3_clim; + data[1] = linoz_o3col_clim; + data[2] = linoz_t_clim; + data[3] = linoz_PmL_clim; + data[4] = linoz_dPmL_dO3; + data[5] = linoz_dPmL_dT; + data[6] = linoz_dPmL_dO3col; + // data[7] = linoz_cariolle_psc; + } + }; // define the different field layouts that will be used for this process @@ -95,6 +124,8 @@ namespace scream::mam_coupling { create_linoz_data_reader ( const std::string& linoz_data_file, LinozReaderParams& linoz_params, + const int ncol, + const const_view_1d const_col_latitudes, const ekat::Comm& comm) { @@ -129,34 +160,47 @@ namespace scream::mam_coupling { auto scalar1d_lev_layout_linoz = make_layout({nlevs_data}, {"lev"}); - const int nvars=1; + const int nvars=NVARS_LINOZ; Field lat (FieldIdentifier("lat", scalar1d_lat_layout_linoz, nondim,io_grid->name())); Field lev (FieldIdentifier("lev", scalar1d_lev_layout_linoz, nondim,io_grid->name())); - - Field o3_clim (FieldIdentifier("o3_clim", scalar2d_layout_linoz, nondim,io_grid->name())); - - o3_clim.allocate_view(); lat.allocate_view(); lev.allocate_view(); std::vector io_fields; io_fields.push_back(lat); io_fields.push_back(lev); - io_fields.push_back(o3_clim); - - linoz_params.latitudes=io_fields[0].get_view(); - linoz_params.levs = io_fields[1].get_view< Real*>(); + // FIXME: units are wrong. for (int ivar = 0; ivar < nvars; ++ivar) { + auto var_name = linoz_var_names[ivar]; + // set and allocate fields + Field f(FieldIdentifier(var_name, scalar2d_layout_linoz, nondim,io_grid->name())); + f.allocate_view(); + io_fields.push_back(f); + // get views linoz_params.data_orig[ivar] = io_fields[ivar+2].get_view(); + // allocate views to store data after horizontal interpolation. + linoz_params.data_horiz[ivar]=view_2d(var_name+"_test", nlevs_data, ncol); } + + linoz_params.latitudes=io_fields[0].get_view(); + linoz_params.levs = io_fields[1].get_view< Real*>(); + + // make a copy of col_latitudes without const Real + view_1d col_latitudes("col",ncol); + Kokkos::deep_copy(col_latitudes, const_col_latitudes); + linoz_params.col_latitudes = col_latitudes; + + // allocate temp views + linoz_params.kupper = view_int_1d("kupper",ncol); + linoz_params.pin = view_2d("pin", ncol,nlevs_data); + return std::make_shared(linoz_data_file, io_grid,io_fields,true); } - static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, - LinozData& linoz_data_out) + static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, LinozData& linoz_data_out) { // FIXME: get this inputs from eamxx interface. const auto col_latitudes = linoz_params.col_latitudes; @@ -187,7 +231,7 @@ namespace scream::mam_coupling { for (int ivar = 0; ivar < nvars; ++ivar) { const auto var_org = linoz_params.data_orig[ivar]; const auto y1 = ekat::subview(var_org,kk); - const auto y2 = ekat::subview(linoz_params.views_horiz[ivar],kk); + const auto y2 = ekat::subview(linoz_params.data_horiz[ivar],kk); horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); } }); @@ -201,7 +245,7 @@ namespace scream::mam_coupling { const int icol = team.league_rank() / linoz_data_nlev; const int ilev = team.league_rank() % linoz_data_nlev; for (int ivar = 0; ivar < nvars; ++ivar) { - const auto input = linoz_params.views_horiz[ivar]; + const auto input = linoz_params.data_horiz[ivar]; const auto output = linoz_data_out.data[ivar]; output(icol, ilev) = input(ilev, icol); }// ivar From 1f28e2569180dd881158fd03043bd6d0551645a1 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sun, 14 Jul 2024 16:47:31 -0600 Subject: [PATCH 08/31] Linoz_reader - Adding linoz_cariolle_pscs. --- ...mxx_mam_microphysics_process_interface.cpp | 18 +- .../src/physics/mam/impl/helper_micro.hpp | 279 +++--------------- 2 files changed, 49 insertions(+), 248 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index b513ae87173..2dc5ddd2bc2 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -161,14 +161,13 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // Tracers group -- do we need this in addition to the tracers above? In any // case, this call should be idempotent, so it can't hurt. add_group("tracers", grid_name, 1, Bundling::Required); - // read linoz files -#if 1 + // Creating a Linoz reader and setting Linoz parameters involves reading data from a file + // and configuring the necessary parameters for the Linoz model. { std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; linoz_reader_ = create_linoz_data_reader(linoz_file_name, linoz_params_, ncol_, col_latitudes_, m_comm); } -#endif } // this checks whether we have the tracers we expect @@ -294,7 +293,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { auto linoz_dPmL_dO3 = buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] auto linoz_dPmL_dT = buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] - auto linoz_cariolle_psc = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] LinozData_end_.init(ncol_,linoz_params_.nlevs); LinozData_end_.allocate_data_views(); @@ -313,9 +312,8 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { linoz_PmL_clim, linoz_dPmL_dO3, linoz_dPmL_dT, - linoz_dPmL_dO3col); - // , - // linoz_cariolle_psc); + linoz_dPmL_dO3col, + linoz_cariolle_pscs); // Load the first month into spa_end. // Note: At the first time step, the data will be moved into spa_beg, @@ -361,7 +359,7 @@ void MAMMicrophysics::run_impl(const double dt) { auto linoz_dPmL_dO3 = buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] auto linoz_dPmL_dT = buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] - auto linoz_cariolle_psc = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] // it's a bit wasteful to store this for all columns, but simpler from an // allocation perspective @@ -370,7 +368,7 @@ void MAMMicrophysics::run_impl(const double dt) { { /* Gather time and state information for interpolation */ auto ts = timestamp()+dt; - /* Update the SPATimeState to reflect the current time, note the addition of dt */ + /* Update the LinozTimeState to reflect the current time, note the addition of dt */ linoz_time_state_.t_now = ts.frac_of_year_in_days(); /* Update time state and if the month has changed, update the data.*/ update_linoz_timestate(ts, @@ -559,7 +557,7 @@ void MAMMicrophysics::run_impl(const double dt) { zenith_angle, pmid, dt, rlats, linoz_o3_clim(icol, k), linoz_t_clim(icol, k), linoz_o3col_clim(icol, k), linoz_PmL_clim(icol, k), linoz_dPmL_dO3(icol, k), linoz_dPmL_dT(icol, k), - linoz_dPmL_dO3col(icol, k), linoz_cariolle_psc(icol, k), + linoz_dPmL_dO3col(icol, k), linoz_cariolle_pscs(icol, k), chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees); diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 58698e56542..baa3e9f75f9 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -11,18 +11,16 @@ namespace scream::mam_coupling { using namespace ShortFieldTagsNames; - // using npack equal to 1. + // using npack equal to 1. using LIV = ekat::LinInterp; using ExeSpace = typename KT::ExeSpace; using ESU = ekat::ExeSpaceUtils; - constexpr int NVARS_LINOZ=7; + constexpr int NVARS_LINOZ=8; const std::vector linoz_var_names={"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", - "dPmL_dT", "dPmL_dO3col"}; - // ,"cariolle_psc"}; - + "dPmL_dT", "dPmL_dO3col","cariolle_pscs"}; struct LinozReaderParams { int nlevs{-1}; @@ -103,9 +101,8 @@ namespace scream::mam_coupling { const view_2d& linoz_PmL_clim, const view_2d& linoz_dPmL_dO3, const view_2d& linoz_dPmL_dT, - const view_2d& linoz_dPmL_dO3col) - // , - // const view_2d& linoz_cariolle_psc) + const view_2d& linoz_dPmL_dO3col, + const view_2d& linoz_cariolle_pscs) { data[0] = linoz_o3_clim; data[1] = linoz_o3col_clim; @@ -114,13 +111,13 @@ namespace scream::mam_coupling { data[4] = linoz_dPmL_dO3; data[5] = linoz_dPmL_dT; data[6] = linoz_dPmL_dO3col; - // data[7] = linoz_cariolle_psc; + data[7] = linoz_cariolle_pscs; } }; // define the different field layouts that will be used for this process - static std::shared_ptr + inline std::shared_ptr create_linoz_data_reader ( const std::string& linoz_data_file, LinozReaderParams& linoz_params, @@ -184,7 +181,7 @@ namespace scream::mam_coupling { linoz_params.data_horiz[ivar]=view_2d(var_name+"_test", nlevs_data, ncol); } - linoz_params.latitudes=io_fields[0].get_view(); + const auto& latitudes_degree = io_fields[0].get_view(); linoz_params.levs = io_fields[1].get_view< Real*>(); // make a copy of col_latitudes without const Real @@ -196,11 +193,32 @@ namespace scream::mam_coupling { linoz_params.kupper = view_int_1d("kupper",ncol); linoz_params.pin = view_2d("pin", ncol,nlevs_data); + const auto& pin = linoz_params.pin; + view_1d latitudes("lat",nlevs_data ); + + const auto& levs = linoz_params.levs; + const auto policy_interp = ESU::get_default_team_policy(ncol, nlevs_data); + const int pi =haero::Constants::pi; + Kokkos::parallel_for("unit_convertion", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + // mbar->pascals + const int icol = team.league_rank(); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), [&] (const Int& kk) { + pin(icol, kk) = levs(kk)*100; + // radians to degrees + if (icol==0){ + latitudes(kk) = latitudes_degree (kk)* pi/180.; + } + }); + }); + Kokkos::fence(); + linoz_params.latitudes=latitudes; + return std::make_shared(linoz_data_file, io_grid,io_fields,true); } - static void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, LinozData& linoz_data_out) + inline void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, LinozData& linoz_data_out) { // FIXME: get this inputs from eamxx interface. const auto col_latitudes = linoz_params.col_latitudes; @@ -213,7 +231,7 @@ namespace scream::mam_coupling { const auto policy_setup = ESU::get_default_team_policy(1, ncol); auto lat = linoz_params.latitudes; - Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + Kokkos::parallel_for("vert_interp_setup_loop", policy_setup, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { // Setup horiz_interp.setup(team, lat, col_latitudes); @@ -254,7 +272,8 @@ namespace scream::mam_coupling { } // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp -static void vert_interp(int ncol, +KOKKOS_INLINE_FUNCTION +void vert_interp(int ncol, int levsiz, int pver, const view_2d& pin, @@ -295,14 +314,14 @@ static void vert_interp(int ncol, } else { Real dpu = pmid(i, k) - pin(i, kupper(i)); Real dpl = pin(i, kupper(i) + 1) - pmid(i, k); - dataout(i, k) = (datain(i, kupper[i]) * dpl + datain(i, kupper(i) + 1) * dpu) / (dpl + dpu); + dataout(i, k) = (datain(i, kupper(i)) * dpl + datain(i, kupper(i) + 1) * dpu) / (dpl + dpu); }// end if } // end col } // end k } // vert_interp -static void perform_vertical_interpolation(const LinozReaderParams& linoz_params, +inline void perform_vertical_interpolation(const LinozReaderParams& linoz_params, const const_view_2d& p_mid, LinozData& non_interpolated_linoz, LinozData& interpolated_linoz) @@ -316,13 +335,7 @@ static void perform_vertical_interpolation(const LinozReaderParams& linoz_params const auto kupper = linoz_params.kupper; const auto levs = linoz_params.levs; const auto pin = linoz_params.pin; - // FIXME: we do not need to update pin every time iteration. - for (int kk = 0; kk < nlevs_linoz; ++kk) - { - const auto pin_kk = Kokkos::subview(pin,Kokkos::ALL,kk); - Kokkos::deep_copy(pin_kk,levs(kk)); - }// i - Kokkos::fence(); + const auto policy_interp = ESU::get_default_team_policy(nvars, 1); Kokkos::parallel_for("vertical_interpolation_linoz", policy_interp, @@ -330,7 +343,7 @@ static void perform_vertical_interpolation(const LinozReaderParams& linoz_params const int ivar = team.league_rank(); const auto var_non_inter = non_interpolated_linoz.data[ivar]; const auto var_inter = interpolated_linoz.data[ivar]; - scream::mam_coupling::vert_interp(ncol, + vert_interp(ncol, nlevs_linoz, nlev, pin, @@ -345,7 +358,7 @@ static void perform_vertical_interpolation(const LinozReaderParams& linoz_params }//perform_vertical_interpolation // This function is based on update_spa_timestate -void static update_linoz_timestate(const util::TimeStamp& ts, +inline void update_linoz_timestate(const util::TimeStamp& ts, LinozTimeState& time_state, std::shared_ptr linoz_reader, const LinozReaderParams& linoz_params, @@ -381,20 +394,20 @@ void static update_linoz_timestate(const util::TimeStamp& ts, } // update_linoz_timestate KOKKOS_INLINE_FUNCTION -static Real linear_interp(const Real& x0, const Real& x1, const Real& t) +Real linear_interp(const Real& x0, const Real& x1, const Real& t) { return (1 - t)*x0 + t*x1; } // linear_interp KOKKOS_INLINE_FUNCTION -static view_1d get_var_column (const LinozData& data, +view_1d get_var_column (const LinozData& data, const int icol, const int ivar) { return ekat::subview(data.data[ivar],icol); } // get_var_column // This function is based on the SPA::perform_time_interpolation function. - static void perform_time_interpolation( + inline void perform_time_interpolation( const LinozTimeState& time_state, const LinozData& data_beg, const LinozData& data_end, @@ -441,215 +454,5 @@ static view_1d get_var_column (const LinozData& data, Kokkos::fence(); } // perform_time_interpolation -#if 0 -void static -perform_vertical_interpolation_linear( - const LinozReaderParams& linoz_params, - const view_2d& p_tgt, - const int ncols, - const int nlevs) -{ - const int nlevs_src = linoz_params.nlevs; - const int nlevs_tgt = nlevs; - - LIV vert_interp(ncols,nlevs_src,nlevs_tgt); - - // We can ||ize over columns as well as over variables and bands - const int num_vars = 1; - const int num_vert_packs = nlevs_tgt; - const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); - - const auto lev = linoz_params.io_fields[1].get_view< Real*>(); - - // Setup the linear interpolation object - Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int icol = team.league_rank(); - // Setup - vert_interp.setup(team, lev, ekat::subview(p_tgt,icol)); - }); - Kokkos::fence(); - - // Now use the interpolation object in || over all variables. - const int outer_iters = ncols*num_vars; - const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); - Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - - const int icol = team.league_rank(); - const auto x1 = lev; - const auto x2 = ekat::subview(p_tgt,icol); - - const auto y1 = ekat::subview(linoz_params.views_horiz_transpose[0],icol); - const auto y2 = ekat::subview(linoz_params.views_vert[0],icol); - vert_interp.lin_interp(team, x1, x2, y1, y2, icol); - }); - Kokkos::fence(); -} -#endif - - } // namespace scream::mam_coupling #endif //EAMXX_MAM_HELPER_MICRO - - // scream::mam_coupling::create_linoz_data_reader(linoz_file_name,linoz_params,m_comm); - - // auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, - // {"lev","lat"}); - - // auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, - // {"lat"}); - // ekat::ParameterList params_Linoz; - // params_Linoz.set("Filename", linoz_file_name); - // // make a list of host views - // std::map host_views_Linoz; - // params_Linoz.set("Skip_Grid_Checks", true); - // params_Linoz.set( - // "Field Names", - // {"o3_clim", - // "lat"}); - // view_2d_host o3_clim_host("o3_clim_host",nlevs_data, nlat_data); - // view_1d_host lat_host("lat_host", nlat_data); - // host_views_Linoz["o3_clim"] = - // view_1d_host(o3_clim_host.data(), o3_clim_host.size()); - // host_views_Linoz["lat"] = - // view_1d_host(lat_host.data(), lat_host.size()); - - // layouts_linoz.emplace("o3_clim", scalar2d_layout_linoz); - // layouts_linoz.emplace("lat", scalar1d_lat_layout_linoz); - - // linoz_reader_=std::make_shared(params_Linoz, grid_, host_views_Linoz, - // layouts_linoz); - - // linoz_reader_ = AtmosphereInput(params_Linoz, grid_, host_views_Linoz, - // layouts_linoz); - - // AtmosphereInput Linoz_reader(params_Linoz, grid_, host_views_Linoz, - // layouts_linoz); - // linoz_reader_ = scream::mam_coupling::create_linoz_data_reader(linoz_file_name); - // linoz_reader_->finalize(); -// #if 0 -// static void vertical_interpolation( LinozReaderParams& linoz_params, -// const view_2d& pmid) -// { -// const int nlev = pmid.extent(1); -// const int ncol = pmid.extent(0); -// const int num_vars = linoz_params.nlevs; -// LIV horiz_interp(num_vars, nlev, ncol); -// const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol); - -// auto lev = linoz_params.io_fields[1].get_view(); -// // We can ||ize over columns as well as over variables and bands -// LIV ver_interp(num_vars, linoz_params.nlevs, nlev); -// const auto policy_setup = ESU::get_default_team_policy(num_vars, nlev); - - - -// } - -// static std::shared_ptr -// create_linoz_data_reader ( -// const std::string& linoz_data_file) -// { - -// using view_1d_host = typename KT::view_1d::HostMirror; -// using view_2d_host = typename KT::view_2d::HostMirror; -// using strvec_t = std::vector; - - -// std::map layouts_linoz; -// // const auto& fname = m_params.get(table_name); -// scorpio::register_file(linoz_data_file,scorpio::Read); -// const int nlevs_data = scorpio::get_dimlen(linoz_data_file,"lev"); -// const int nlat_data = scorpio::get_dimlen(linoz_data_file,"lat"); -// scorpio::release_file(linoz_data_file); -// std::cout << "nlevs_data" << nlevs_data << "\n"; -// std::cout << "nlat_data" << nlat_data << "\n"; - -// auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, -// {"lev","lat"}); - -// auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, -// {"lat"}); -// ekat::ParameterList params_Linoz; -// params_Linoz.set("Filename", linoz_data_file); -// // make a list of host views -// std::map host_views_Linoz; -// params_Linoz.set("Skip_Grid_Checks", true); -// params_Linoz.set( -// "Field Names", -// {"o3_clim", -// "lat"}); -// view_2d_host o3_clim_host("o3_clim_host",nlevs_data, nlat_data); -// view_1d_host lat_host("lat_host", nlat_data); -// host_views_Linoz["o3_clim"] = -// view_1d_host(o3_clim_host.data(), o3_clim_host.size()); -// host_views_Linoz["lat"] = -// view_1d_host(lat_host.data(), lat_host.size()); - -// layouts_linoz.emplace("o3_clim", scalar2d_layout_linoz); -// layouts_linoz.emplace("lat", scalar1d_lat_layout_linoz); - -// return std::make_shared(params_Linoz, grid_, host_views_Linoz, -// layouts_linoz); -// } -// #endif - -// #if 0 -// std::cout << "o3_clim: " << o3_clim_host(0,0) << "\n"; -// std::cout << "lat: " << lat_host(0) << "\n"; - -// std::cout << "col_latitudes_.extents(0): " << col_latitudes_.extent(0) << "\n"; -// std::cout << "ncol_: " << ncol_ << "\n"; - - - -// using LIV = ekat::LinInterp; -// using ExeSpace = typename KT::ExeSpace; -// using ESU = ekat::ExeSpaceUtils; - -// // We can ||ize over columns as well as over variables and bands -// const int num_vars = nlevs_data; -// LIV horiz_interp(num_vars, nlat_data, ncol_); -// const auto policy_setup = ESU::get_default_team_policy(num_vars, ncol_); -// // Setup the linear interpolation object -// // auto& col_latitudes= col_latitudes_; -// col_latitudes_copy_ = view_1d("col",ncol_); -// Kokkos::deep_copy(col_latitudes_copy_, col_latitudes_); - -// view_1d lat("lat",nlat_data ); -// Kokkos::deep_copy(lat, lat_host); - -// Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, -// KOKKOS_LAMBDA(typename LIV::MemberType const& team) { -// // Setup -// horiz_interp.setup(team, lat, col_latitudes); -// }); -// Kokkos::fence(); - -// // Now use the interpolation object in || over all variables. -// const int outer_iters = nlevs_data; -// const auto policy_interp = ESU::get_default_team_policy(outer_iters, ncol_); -// Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, -// KOKKOS_LAMBDA(typename LIV::MemberType const& team) { -// const int kk = team.league_rank(); -// const auto x1 = lat; -// const auto x2 = col_latitudes; -// const auto y1 = ekat::subview(o3_clim_host,kk); -// const auto y2 = ekat::subview(o3_clim_test,kk); -// horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); -// }); -// Kokkos::fence(); -// std::cout << "o3_clim_host \n"; -// for (int i = 0; i < nlat_data; ++i) -// { -// std::cout < Date: Mon, 15 Jul 2024 10:28:46 -0600 Subject: [PATCH 09/31] Linoz_reader - Getting linoz file name from namelist Linoz_reader - Getting inputs for chlorine_loading from namelist. photo_table_reader-Getting photo table from namelist and using scream_scorpio interface to read data. --- ...mxx_mam_microphysics_process_interface.cpp | 76 +++++++----- ...mxx_mam_microphysics_process_interface.hpp | 7 ++ .../physics/mam/impl/gas_phase_chemistry.cpp | 109 +++++++++++++++++- .../src/physics/mam/impl/helper_micro.hpp | 70 ++++++++++- .../mam/aero_microphys/input.yaml | 4 + 5 files changed, 234 insertions(+), 32 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 2dc5ddd2bc2..7d521e07c73 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -75,11 +75,11 @@ void MAMMicrophysics::set_defaults_() { // e3smv2/bld/namelist_files/namelist_defaults_eam.xml // photolysis - set_file_location(config_.photolysis.rsf_file, "../waccm/phot/RSF_GT200nm_v3.0_c080811.nc"); - set_file_location(config_.photolysis.xs_long_file, "../waccm/phot/temp_prs_GT200nm_JPL10_c130206.nc"); + // set_file_location(config_.photolysis.rsf_file, "../waccm/phot/RSF_GT200nm_v3.0_c080811.nc"); + // set_file_location(config_.photolysis.xs_long_file, "../waccm/phot/temp_prs_GT200nm_JPL10_c130206.nc"); // stratospheric chemistry - set_file_location(config_.linoz.chlorine_loading_file, "../cam/chem/trop_mozart/ub/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"); + // set_file_location(config_.linoz.chlorine_loading_file, "../cam/chem/trop_mozart/ub/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"); // dry deposition set_file_location(config_.drydep.srf_file, "../cam/chem/trop_mam/atmsrf_ne4pg2_200527.nc"); @@ -164,9 +164,12 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // Creating a Linoz reader and setting Linoz parameters involves reading data from a file // and configuring the necessary parameters for the Linoz model. { - std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + std::string linoz_file_name = + m_params.get("mam4_linoz_file_name"); linoz_reader_ = create_linoz_data_reader(linoz_file_name, linoz_params_, ncol_, col_latitudes_, m_comm); + } } @@ -259,9 +262,12 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { } // create our photolysis rate calculation table - /*photo_table_ = impl::read_photo_table(get_comm(), - config_.photolysis.rsf_file, - config_.photolysis.xs_long_file);*/ + const std::string rsf_file = + m_params.get("mam4_rsf_file"); + const std::string xs_long_file = + m_params.get("mam4_xs_long_file"); + + photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); // FIXME: read relevant land use data from drydep surface file @@ -327,6 +333,16 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { LinozData_end_, interpolated_Linoz_data_); + // const std::string linoz_chlorine_file = "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; + // auto ts = timestamp(); + // int chlorine_loading_ymd=20100101; + auto ts = timestamp(); + std::string linoz_chlorine_file = + m_params.get("mam4_linoz_chlorine_file"); + int chlorine_loading_ymd = + m_params.get("mam4_chlorine_loading_ymd"); + scream::mam_coupling::create_linoz_chlorine_reader (linoz_chlorine_file, ts, chlorine_loading_ymd, + chlorine_values_, chlorine_time_secs_ ); } } @@ -365,9 +381,10 @@ void MAMMicrophysics::run_impl(const double dt) { // allocation perspective auto o3_col_dens = buffer_.scratch[8]; + auto ts = timestamp()+dt; { /* Gather time and state information for interpolation */ - auto ts = timestamp()+dt; + /* Update the LinozTimeState to reflect the current time, note the addition of dt */ linoz_time_state_.t_now = ts.frac_of_year_in_days(); /* Update time state and if the month has changed, update the data.*/ @@ -387,8 +404,9 @@ void MAMMicrophysics::run_impl(const double dt) { dry_atm_.p_mid, LinozData_out_, interpolated_Linoz_data_); - } + const Real chlorine_loading = scream::mam_coupling::chlorine_loading_advance(ts, chlorine_values_, + chlorine_time_secs_); const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; @@ -409,11 +427,11 @@ void MAMMicrophysics::run_impl(const double dt) { // fetch column-specific atmosphere state data auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); - /*auto z_iface = ekat::subview(dry_atm.z_iface, icol); + auto z_iface = ekat::subview(dry_atm.z_iface, icol); Real phis = dry_atm.phis(icol); // set surface state data - /*haero::Surface sfc{}; + haero::Surface sfc{}; // fetch column-specific subviews into aerosol prognostics mam4::Prognostics progs = mam_coupling::interstitial_aerosols_for_column(dry_aero, icol); @@ -423,7 +441,7 @@ void MAMMicrophysics::run_impl(const double dt) { // calculate o3 column densities (first component of col_dens in Fortran code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); - impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); + //impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); // set up photolysis work arrays for this column. mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays; @@ -444,10 +462,10 @@ void MAMMicrophysics::run_impl(const double dt) { constexpr int extcnt = mam4::gas_chemistry::extcnt; view_2d extfrc; // FIXME: where to allocate? (nlev, extcnt) mam4::mo_setext::Forcing forcings[extcnt]; // FIXME: forcings seem to require file data - mam4::mo_setext::extfrc_set(forcings, extfrc);*/ + //mam4::mo_setext::extfrc_set(forcings, extfrc); // compute aerosol microphysics on each vertical level within this column - /*Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { + Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int gas_pcnst = mam_coupling::gas_pcnst(); @@ -468,12 +486,12 @@ void MAMMicrophysics::run_impl(const double dt) { // mozart/mo_gas_phase_chemdr.F90) Real q[gas_pcnst] = {}; Real qqcw[gas_pcnst] = {}; - mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); + //mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); // convert mass mixing ratios to volume mixing ratios (VMR), equivalent // to tracer mixing ratios (TMR)) Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); + //mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); // aerosol/gas species tendencies (output) Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; @@ -492,20 +510,21 @@ void MAMMicrophysics::run_impl(const double dt) { //--------------------- // Gas Phase Chemistry //--------------------- + // Real photo_rates_k[mam4::mo_photo::phtcnt]; for (int i = 0; i < mam4::mo_photo::phtcnt; ++i) { photo_rates_k[i] = photo_rates(k, i); } - Real extfrc_k[extcnt]; + /*Real extfrc_k[extcnt]; for (int i = 0; i < extcnt; ++i) { extfrc_k[i] = extfrc(k, i); - } + }*/ constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" // NOTE: we compute invariants here and pass them out to use later with // NOTE: setsox Real invariants[nfs]; - impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k, extfrc_k, vmr, invariants); + /*impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, + photo_rates_k, extfrc_k, vmr, invariants);*/ //---------------------- // Aerosol microphysics @@ -519,8 +538,8 @@ void MAMMicrophysics::run_impl(const double dt) { const Real mbar = haero::Constants::molec_weight_dry_air; constexpr int indexm = 0; // FIXME: index of xhnm in invariants array (??) Real cldnum = 0.0; // FIXME: droplet number concentration: where do we get this? - setsox_single_level(loffset, dt, pmid, pdel, temp, mbar, lwc(k), - cldfrac, cldnum, invariants[indexm], config.setsox, vmrcw, vmr); + /*setsox_single_level(loffset, dt, pmid, pdel, temp, mbar, lwc(k), + cldfrac, cldnum, invariants[indexm], config.setsox, vmrcw, vmr);*/ // calculate aerosol water content using water uptake treatment // * dry and wet diameters [m] @@ -530,14 +549,14 @@ void MAMMicrophysics::run_impl(const double dt) { Real dgncur_awet[num_modes] = {}; Real wetdens[num_modes] = {}; Real qaerwat[num_modes] = {}; - impl::compute_water_content(progs, k, qv, temp, pmid, dgncur_a, dgncur_awet, wetdens, qaerwat); + //impl::compute_water_content(progs, k, qv, temp, pmid, dgncur_a, dgncur_awet, wetdens, qaerwat); // do aerosol microphysics (gas-aerosol exchange, nucleation, coagulation) /*impl::modal_aero_amicphys_intr(config.amicphys, step, dt, t, pmid, pdel, zm, pblh, qv, cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a, dgncur_awet, - wetdens, qaerwat); + wetdens, qaerwat);*/ //----------------- // LINOZ chemistry @@ -548,9 +567,6 @@ void MAMMicrophysics::run_impl(const double dt) { Real do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees; - // FIXME: Need to get chlorine loading data from file - Real chlorine_loading = 0.0; - Real rlats = col_lat * M_PI / 180.0; // convert column latitude to radians int o3_ndx = 0; // index of "O3" in solsym array (in EAM) mam4::lin_strat_chem::lin_strat_chem_solve_kk(o3_col_dens_i(k), temp, @@ -582,9 +598,9 @@ void MAMMicrophysics::run_impl(const double dt) { //mam4::drydep::drydep_xactive(...); // transfer updated prognostics from work arrays - mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); - mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k); - });*/ + //mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); + //mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k);*/ + }); }); // postprocess output diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 563af4401b6..bdac191cc6c 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -38,6 +38,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { using view_1d_int = typename KT::template view_1d; using view_1d = typename KT::template view_1d; using view_2d = typename KT::template view_2d; + using view_3d = typename KT::template view_3d; using const_view_1d = typename KT::template view_1d; using const_view_2d = typename KT::template view_2d; @@ -236,6 +237,12 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::LinozData LinozData_end_; mam_coupling::LinozData LinozData_out_; mam_coupling::LinozData interpolated_Linoz_data_; + // + view_2d work_photo_table_; + std::vector chlorine_values_; + std::vector chlorine_time_secs_; + view_3d photo_rates_; + view_2d lwc_; }; // MAMMicrophysics diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index 5401fa5e3da..128cff0337b 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -94,7 +94,113 @@ void populate_etfphot(HostView1D we, HostView1D etfphot) { // FIXME: zero the photon flux for now Kokkos::deep_copy(etfphot, 0); } +// This version uses scream_scorpio_interface to read netcdf files. +mam4::mo_photo::PhotoTableData read_photo_table(const std::string& rsf_file, + const std::string& xs_long_file) { + + +// set up the lng_indexer and pht_alias_mult_1 views based on our +// (hardwired) chemical mechanism +HostViewInt1D lng_indexer_h("lng_indexer(host)", mam4::mo_photo::phtcnt); + + +int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions +scorpio::register_file(rsf_file,scorpio::Read); +// read and broadcast dimension data +nump = scorpio::get_dimlen(rsf_file,"numz"); +numsza = scorpio::get_dimlen(rsf_file, "numsza"); +numalb = scorpio::get_dimlen(rsf_file, "numalb"); +numcolo3 = scorpio::get_dimlen(rsf_file, "numcolo3fact"); + +scorpio::register_file(xs_long_file,scorpio::Read); +nt = scorpio::get_dimlen(xs_long_file, "numtemp"); +nw = scorpio::get_dimlen(xs_long_file, "numwl"); +np_xs = scorpio::get_dimlen(xs_long_file, "numprs"); + +//FIXME: hard-coded for only one photo reaction. +std::string rxt_names[1] = {"jh2o2"}; +int numj = 1; +// allocate the photolysis table +auto table = mam4::mo_photo::create_photo_table_data(nw, nt, np_xs, numj, + nump, numsza, numcolo3, + numalb); + +// allocate host views for table data +auto rsf_tab_h = Kokkos::create_mirror_view(table.rsf_tab); +auto xsqy_h = Kokkos::create_mirror_view(table.xsqy); +auto sza_h = Kokkos::create_mirror_view(table.sza); +auto alb_h = Kokkos::create_mirror_view(table.alb); +auto press_h = Kokkos::create_mirror_view(table.press); +auto colo3_h = Kokkos::create_mirror_view(table.colo3); +auto o3rat_h = Kokkos::create_mirror_view(table.o3rat); +auto etfphot_h = Kokkos::create_mirror_view(table.etfphot); +auto prs_h = Kokkos::create_mirror_view(table.prs); + +// read file data into our host views +scorpio::read_var(rsf_file, "pm", press_h.data()); +scorpio::read_var(rsf_file, "sza", sza_h.data()); +scorpio::read_var(rsf_file, "alb", alb_h.data()); +scorpio::read_var(rsf_file, "colo3fact", o3rat_h.data()); +scorpio::read_var(rsf_file, "colo3", colo3_h.data()); +// it produces an error. +scorpio::read_var(rsf_file, "RSF", rsf_tab_h.data()); +scorpio::read_var(xs_long_file, "pressure", prs_h.data()); + +// read xsqy data (using lng_indexer_h for the first index) +//FIXME: hard-coded for only one photo reaction. +for (int m = 0; m < mam4::mo_photo::phtcnt; ++m) { + auto xsqy_ndx_h = ekat::subview(xsqy_h, m); + scorpio::read_var(xs_long_file, rxt_names[m], xsqy_h.data()); +} + +// populate etfphot by rebinning solar data +HostView1D wc_h("wc", nw), wlintv_h("wlintv", nw), we_h("we", nw+1); + +scorpio::read_var(rsf_file, "wc", wc_h.data()); +scorpio::read_var(rsf_file, "wlintv", wlintv_h.data()); +for (int i = 0; i < nw; ++i) { + we_h(i) = wc_h(i) - 0.5 * wlintv_h(i); +} +we_h(nw) = wc_h(nw-1) - 0.5 * wlintv_h(nw-1); +populate_etfphot(we_h, etfphot_h); +scorpio::release_file(rsf_file); +scorpio::release_file(xs_long_file); + +// copy host photolysis table into place on device +Kokkos::deep_copy(table.rsf_tab, rsf_tab_h); +Kokkos::deep_copy(table.xsqy, xsqy_h); +Kokkos::deep_copy(table.sza, sza_h); +Kokkos::deep_copy(table.alb, alb_h); +Kokkos::deep_copy(table.press, press_h); +Kokkos::deep_copy(table.colo3, colo3_h); +Kokkos::deep_copy(table.o3rat, o3rat_h); +Kokkos::deep_copy(table.etfphot, etfphot_h); +Kokkos::deep_copy(table.prs, prs_h); +// set pht_alias_mult_1 to 1 +Kokkos::deep_copy(table.pht_alias_mult_1, 1.0); +// Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); + + // compute gradients (on device) + Kokkos::parallel_for("del_p", nump-1, KOKKOS_LAMBDA(int i) { + table.del_p(i) = 1.0/::abs(table.press(i)- table.press(i+1)); + }); + Kokkos::parallel_for("del_sza", numsza-1, KOKKOS_LAMBDA(int i) { + table.del_sza(i) = 1.0/(table.sza(i+1) - table.sza(i)); + }); + Kokkos::parallel_for("del_alb", numalb-1, KOKKOS_LAMBDA(int i) { + table.del_alb(i) = 1.0/(table.alb(i+1) - table.alb(i)); + }); + Kokkos::parallel_for("del_o3rat", numcolo3-1, KOKKOS_LAMBDA(int i) { + table.del_o3rat(i) = 1.0/(table.o3rat(i+1) - table.o3rat(i)); + }); + Kokkos::parallel_for("dprs", np_xs-1, KOKKOS_LAMBDA(int i) { + table.dprs(i) = 1.0/(table.prs(i) - table.prs(i+1)); + }); +return table; +} + +#if 0 // ON HOST, reads the photolysis table (used for gas phase chemistry) from the // files with the given names mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, @@ -122,6 +228,7 @@ mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, nt = nc_dimension(xs_long_file, xs_long_id, "numtemp"); nw = nc_dimension(xs_long_file, xs_long_id, "numwl"); np_xs = nc_dimension(xs_long_file, xs_long_id, "numprs"); + std::cout <; + using view_1d_host = typename KT::view_1d::HostMirror; + constexpr int NVARS_LINOZ=8; const std::vector linoz_var_names={"o3_clim", "o3col_clim", @@ -201,9 +203,10 @@ namespace scream::mam_coupling { const int pi =haero::Constants::pi; Kokkos::parallel_for("unit_convertion", policy_interp, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - // mbar->pascals + const int icol = team.league_rank(); Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), [&] (const Int& kk) { + // mbar->pascals pin(icol, kk) = levs(kk)*100; // radians to degrees if (icol==0){ @@ -454,5 +457,70 @@ view_1d get_var_column (const LinozData& data, Kokkos::fence(); } // perform_time_interpolation + // time[3]={year,month, day} + inline util::TimeStamp convert_date(const int date) + { + constexpr int ten_thousand = 10000; + constexpr int one_hundred = 100; + + int year = date / ten_thousand; + int month = (date-year*ten_thousand)/one_hundred; + int day = date-year*ten_thousand-month*one_hundred; + return util::TimeStamp(year,month,day,0,0,0); + } + // FIXME: check if this function is implemented in eamxx + // Assumes 365 days/year, 30 days/month + inline int compute_days(const util::TimeStamp& ts ) + { + return ts.get_year()*365 + ts.get_month()*30 + ts.get_day(); + } + + inline void create_linoz_chlorine_reader( + const std::string& linoz_chlorine_file, + const util::TimeStamp& model_time, + const int chlorine_loading_ymd, // in format YYYYMMDD + std::vector& values, + std::vector& time_secs + ) + { + auto time_stamp_beg = convert_date(chlorine_loading_ymd); + + const int offset_time = compute_days(time_stamp_beg) - compute_days(model_time); + scorpio::register_file(linoz_chlorine_file,scorpio::Read); + const int nlevs_time = scorpio::get_time_len(linoz_chlorine_file); + for (int itime = 0; itime < nlevs_time; ++itime) + { + int date; + scorpio::read_var(linoz_chlorine_file,"date",&date,itime); + if (date >= chlorine_loading_ymd ) { + Real value; + scorpio::read_var(linoz_chlorine_file,"chlorine_loading",&value, itime); + values.push_back(value); + auto time_stamp = convert_date(date); + time_secs.push_back(compute_days(time_stamp)-offset_time); + } + }// end itime + scorpio::release_file(linoz_chlorine_file); + } + + inline Real chlorine_loading_advance(const util::TimeStamp& ts, + std::vector& values, + std::vector& time_secs) + { + + const int current_time = compute_days(ts); + int index=0; + // update index + for(int i=0; i < int(values.size()); i++){ + if (current_time > time_secs[i] ) { + index =i; + break; + } + }// + + const Real delt = ( current_time - time_secs[index] ) / ( time_secs[index+1] - time_secs[index] ); + return values[index] + delt*( values[index+1] - values[index] ); + } + } // namespace scream::mam_coupling #endif //EAMXX_MAM_HELPER_MICRO diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 89426776ac6..31ee615be43 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -10,6 +10,10 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] + mam4_aero_microphys: + mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc + mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + mam4_chlorine_loading_ymd : 20100101 grids_manager: Type: Mesh Free From d3e7585b5fe95a732d078ff2889ee36186cb5795 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 19 Jul 2024 15:17:32 -0600 Subject: [PATCH 10/31] microphysics- Computing liquid water cloud content. microphysics - reader for tracers. microphysics - time interpolation. microphysics - Using array of views instead of std::vector. microphysics - Creating TracerData. microphysics - Adding perform_vertical_interpolation. --- ...mxx_mam_microphysics_process_interface.cpp | 99 ++++- ...mxx_mam_microphysics_process_interface.hpp | 8 + .../physics/mam/impl/gas_phase_chemistry.cpp | 4 +- .../src/physics/mam/impl/helper_micro.hpp | 342 ++++++++++++++++++ .../mam/aero_microphys/input.yaml | 3 +- 5 files changed, 444 insertions(+), 12 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 7d521e07c73..bb4f21e3da4 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -171,6 +171,22 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ linoz_params_, ncol_, col_latitudes_, m_comm); } + std::string my_file="oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc"; + std::string spa_map_file=""; + std::vector var_names{"O3","HO2","NO3","OH"}; + TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names); + TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,my_file); + + // 3 Read in hyam/hybm in start/end data, and pad them + const auto io_grid = TracerHorizInterp_->get_src_grid(); + Field hyam(FieldIdentifier("hyam",io_grid->get_vertical_layout(true),nondim,io_grid->name())); + Field hybm(FieldIdentifier("hybm",io_grid->get_vertical_layout(true),nondim,io_grid->name())); + hyam.allocate_view(); + hybm.allocate_view(); + AtmosphereInput hvcoord_reader(my_file,io_grid,{hyam,hybm},true); + hvcoord_reader.read_variables(); + hvcoord_reader.finalize(); + } // this checks whether we have the tracers we expect @@ -344,6 +360,36 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { scream::mam_coupling::create_linoz_chlorine_reader (linoz_chlorine_file, ts, chlorine_loading_ymd, chlorine_values_, chlorine_time_secs_ ); } + + const int photo_table_len = get_photo_table_work_len(photo_table_); + work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); + + // here's where we store per-column photolysis rates + photo_rates_ = view_3d("photo_rates", ncol_, nlev_, mam4::mo_photo::phtcnt); + // liquid water cloud content + lwc_= view_2d("liquid_water_cloud_content", ncol_, nlev_); + + + // + // Load the first month into spa_end. + // Note: At the first time step, the data will be moved into spa_beg, + // and spa_end will be reloaded from file with the new month. + const int curr_month = timestamp().get_month()-1; // 0-based + const int nvars = 4; + const auto io_grid = TracerHorizInterp_->get_src_grid(); + const int num_cols_io = io_grid->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io = io_grid->get_num_vertical_levels(); // Number of levels per column + + tracer_data_end_.init(num_cols_io, num_levs_io, nvars); + scream::mam_coupling::update_tracer_data_from_file(TracerDataReader_, + timestamp(),curr_month, *TracerHorizInterp_, tracer_data_end_); + + tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); + tracer_data_beg_.allocate_data_views(); + + tracer_data_out_.init(num_cols_io, num_levs_io, nvars); + tracer_data_out_.allocate_data_views(); + } void MAMMicrophysics::run_impl(const double dt) { @@ -363,10 +409,6 @@ void MAMMicrophysics::run_impl(const double dt) { // NOTE: nothing depends on simulation time (yet), so we can just use zero for now double t = 0.0; - // here's where we store per-column photolysis rates - using View2D = haero::DeviceType::view_2d; - View2D photo_rates("photo_rates", nlev_, mam4::mo_photo::phtcnt); - // climatology data for linear stratospheric chemistry auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] auto linoz_o3col_clim = buffer_.scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] @@ -404,10 +446,33 @@ void MAMMicrophysics::run_impl(const double dt) { dry_atm_.p_mid, LinozData_out_, interpolated_Linoz_data_); + + } const Real chlorine_loading = scream::mam_coupling::chlorine_loading_advance(ts, chlorine_values_, chlorine_time_secs_); + { + /* Gather time and state information for interpolation */ + auto ts = timestamp()+dt; + /* Update the SPATimeState to reflect the current time, note the addition of dt */ + linoz_time_state_.t_now = ts.frac_of_year_in_days(); + /* Update time state and if the month has changed, update the data.*/ + scream::mam_coupling::update_tracer_timestate( + TracerDataReader_, + ts, + *TracerHorizInterp_, + linoz_time_state_, + tracer_data_beg_, + tracer_data_end_); + + scream::mam_coupling::perform_time_interpolation( + linoz_time_state_, + tracer_data_beg_, + tracer_data_end_, + tracer_data_out_); + } + const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; mam_coupling::AerosolState &dry_aero = dry_aero_; @@ -418,6 +483,10 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: read relevant linoz climatology data from file(s) based on time // FIXME: read relevant chlorine loading data from file based on time + const auto& work_photo_table = work_photo_table_; + const auto& photo_rates = photo_rates_; + + const auto& lwc =lwc_; // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const ThreadTeam& team) { @@ -441,11 +510,20 @@ void MAMMicrophysics::run_impl(const double dt) { // calculate o3 column densities (first component of col_dens in Fortran code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); +<<<<<<< HEAD //impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); +======= + // impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); +>>>>>>> microphysics - Adding photo table paths to input file. // set up photolysis work arrays for this column. - mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays; + mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; // FIXME: set views here + const auto& work_photo_table_icol = ekat::subview(work_photo_table, icol); + // set work view using 1D photo_work_arrays_icol + mam4::mo_photo::set_photo_table_work_arrays(photo_table, + work_photo_table_icol, + photo_work_arrays_icol); // ... look up photolysis rates from our table // NOTE: the table interpolation operates on an entire column of data, so we @@ -453,10 +531,11 @@ void MAMMicrophysics::run_impl(const double dt) { Real zenith_angle = 0.0; // FIXME: need to get this from EAMxx [radians] Real surf_albedo = 0.0; // FIXME: surface albedo Real esfact = 0.0; // FIXME: earth-sun distance factor - mam4::ColumnView lwc; // FIXME: liquid water cloud content: where do we get this? - mam4::mo_photo::table_photo(photo_rates, atm.pressure, atm.hydrostatic_dp, - atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, lwc, - atm.cloud_fraction, esfact, photo_table, photo_work_arrays); + const auto& photo_rates_icol = ekat::subview(photo_rates, icol); + + mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, atm.hydrostatic_dp, + atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, lwc_icol, + atm.cloud_fraction, esfact, photo_table, photo_work_arrays_icol); // compute external forcings at time t(n+1) [molecules/cm^3/s] constexpr int extcnt = mam4::gas_chemistry::extcnt; @@ -513,7 +592,7 @@ void MAMMicrophysics::run_impl(const double dt) { // Real photo_rates_k[mam4::mo_photo::phtcnt]; for (int i = 0; i < mam4::mo_photo::phtcnt; ++i) { - photo_rates_k[i] = photo_rates(k, i); + photo_rates_k[i] = photo_rates_icol(k, i); } /*Real extfrc_k[extcnt]; for (int i = 0; i < extcnt; ++i) { diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index bdac191cc6c..4c12576af42 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -244,6 +244,14 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_3d photo_rates_; view_2d lwc_; + std::shared_ptr TracerDataReader_; + std::shared_ptr TracerHorizInterp_; + mam_coupling::TracerData tracer_data_end_; + mam_coupling::TracerData tracer_data_beg_; + mam_coupling::TracerData tracer_data_out_; + + + }; // MAMMicrophysics } // namespace scream diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index 128cff0337b..592da804464 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -104,6 +104,7 @@ mam4::mo_photo::PhotoTableData read_photo_table(const std::string& rsf_file, HostViewInt1D lng_indexer_h("lng_indexer(host)", mam4::mo_photo::phtcnt); + int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions scorpio::register_file(rsf_file,scorpio::Read); // read and broadcast dimension data @@ -120,6 +121,7 @@ np_xs = scorpio::get_dimlen(xs_long_file, "numprs"); //FIXME: hard-coded for only one photo reaction. std::string rxt_names[1] = {"jh2o2"}; int numj = 1; +lng_indexer_h(0)=0; // allocate the photolysis table auto table = mam4::mo_photo::create_photo_table_data(nw, nt, np_xs, numj, nump, numsza, numcolo3, @@ -178,7 +180,7 @@ Kokkos::deep_copy(table.etfphot, etfphot_h); Kokkos::deep_copy(table.prs, prs_h); // set pht_alias_mult_1 to 1 Kokkos::deep_copy(table.pht_alias_mult_1, 1.0); -// Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); +Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); // compute gradients (on device) Kokkos::parallel_for("del_p", nump-1, KOKKOS_LAMBDA(int i) { diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 616aa9509f0..617067e1d45 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -3,6 +3,9 @@ #include "share/io/scorpio_input.hpp" #include "share/io/scream_scorpio_interface.hpp" +#include "share/grid/remap/coarsening_remapper.hpp" +#include "share/grid/remap/refining_remapper_p2p.hpp" +#include "share/grid/remap/identity_remapper.hpp" #include "share/grid/point_grid.hpp" #include #include @@ -19,6 +22,7 @@ namespace scream::mam_coupling { using view_1d_host = typename KT::view_1d::HostMirror; constexpr int NVARS_LINOZ=8; + constexpr int MAX_NVARS_TRACER=5; const std::vector linoz_var_names={"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", @@ -60,6 +64,61 @@ namespace scream::mam_coupling { Real days_this_month; }; // LinozTimeState + struct TracerData{ + TracerData() = default; + TracerData(const int ncol, const int nlev, const int nvars) + { + init (ncol,nlev, nvars); + } + void init (const int ncol, + const int nlev, + const int nvars){ + ncol_=ncol; + nlev_=nlev; + nvars_=nvars; + EKAT_REQUIRE_MSG (nvars_ <= int(MAX_NVARS_TRACER), + "Error! Number of variables is bigger than NVARS_MAXTRACER. \n"); + } + + int ncol_{-1}; + int nlev_{-1}; + int nvars_{-1}; + view_2d data[MAX_NVARS_TRACER]; + view_1d ps; + + void allocate_data_views() + { + EKAT_REQUIRE_MSG (ncol_ != int(-1), + "Error! ncols has not been set. \n"); + EKAT_REQUIRE_MSG (nlev_ !=int(-1), + "Error! nlevs has not been set. \n"); + + for (int ivar = 0; ivar< nvars_; ++ivar) { + data[ivar] = view_2d("linoz_1",ncol_,nlev_); + } + } //allocate_data_views + + void allocate_ps() + { + ps = view_1d("ps",ncol_); + } + + void set_data_views(view_2d list_of_views[]) + { + for (int ivar = 0; ivar< nvars_; ++ivar) { + EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, + "Error! Insufficient memory size.\n"); + data[ivar] =list_of_views[ivar]; + } + } + + void set_data_ps(const view_1d& ps_in) + { + ps = ps_in; + } + + }; + struct LinozData { LinozData() = default; @@ -522,5 +581,288 @@ view_1d get_var_column (const LinozData& data, return values[index] + delt*( values[index+1] - values[index] ); } +inline +std::shared_ptr +create_horiz_remapper ( + const std::shared_ptr& model_grid, + const std::string& trace_data_file, + const std::string& map_file, + std::vector& var_names + ) +{ + using namespace ShortFieldTagsNames; + + scorpio::register_file(trace_data_file,scorpio::Read); + const int nlevs_data = scorpio::get_dimlen(trace_data_file,"lev"); + const int ncols_data = scorpio::get_dimlen(trace_data_file,"ncol"); + scorpio::release_file(trace_data_file); + + // We could use model_grid directly if using same num levels, + // but since shallow clones are cheap, we may as well do it (less lines of code) + auto horiz_interp_tgt_grid = model_grid->clone("tracer_horiz_interp_tgt_grid",true); + horiz_interp_tgt_grid->reset_num_vertical_lev(nlevs_data); + + const int ncols_model = model_grid->get_num_global_dofs(); + std::shared_ptr remapper; + if (ncols_data==ncols_model) { + remapper = std::make_shared(horiz_interp_tgt_grid,IdentityRemapper::SrcAliasTgt); + } else { + EKAT_REQUIRE_MSG (ncols_data<=ncols_model, + "Error! We do not allow to coarsen spa data to fit the model. We only allow\n" + " spa data to be at the same or coarser resolution as the model.\n"); + // We must have a valid map file + EKAT_REQUIRE_MSG (map_file!="", + "ERROR: Spa data is on a different grid than the model one,\n" + " but spa_remap_file is missing from SPA parameter list."); + + remapper = std::make_shared(horiz_interp_tgt_grid,map_file); + } + + remapper->registration_begins(); + const auto tgt_grid = remapper->get_tgt_grid(); + const auto layout_2d = tgt_grid->get_2d_scalar_layout(); + const auto layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); + const auto nondim = ekat::units::Units::nondimensional(); + + + for(auto var_name : var_names){ + Field ifield(FieldIdentifier(var_name, layout_3d_mid, nondim,tgt_grid->name())); + ifield.allocate_view(); + remapper->register_field_from_tgt (ifield); + } + + Field ps (FieldIdentifier("PS", layout_2d, nondim,tgt_grid->name())); + ps.allocate_view(); + remapper->register_field_from_tgt (ps); + remapper->registration_ends(); + + return remapper; + +} // create_horiz_remapper + +inline +std::shared_ptr +create_tracer_data_reader +( + const std::shared_ptr& horiz_remapper, + const std::string& tracer_data_file) +{ + std::vector io_fields; + for (int i=0; iget_num_fields(); ++i) { + io_fields.push_back(horiz_remapper->get_src_field(i)); + } + const auto io_grid = horiz_remapper->get_src_grid(); + return std::make_shared(tracer_data_file,io_grid,io_fields,true); +} // create_tracer_data_reader + +inline +void +update_tracer_data_from_file( + std::shared_ptr& scorpio_reader, + const util::TimeStamp& ts, + const int time_index, // zero-based + AbstractRemapper& tracer_horiz_interp, + TracerData& tracer_data) +{ + // 1. read from field + scorpio_reader->read_variables(time_index); + // 2. Run the horiz remapper (it is a do-nothing op if spa data is on same grid as model) + tracer_horiz_interp.remap(/*forward = */ true); + // + const int nvars =tracer_data.nvars_; + // Recall, the fields are registered in the order: ps, ccn3, g_sw, ssa_sw, tau_sw, tau_lw + // 3. Copy from the tgt field of the remapper into the spa_data +// auto ps = tracer_horiz_interp.get_tgt_field (0).get_view(); + // + for (int i = 0; i < nvars; ++i) { + tracer_data.data[i] = tracer_horiz_interp.get_tgt_field (i).get_view< Real**>(); + } + +} // update_tracer_data_from_file +inline void +update_tracer_timestate( + std::shared_ptr& scorpio_reader, + const util::TimeStamp& ts, + AbstractRemapper& tracer_horiz_interp, + LinozTimeState& time_state, + TracerData& data_tracer_beg, + TracerData& data_tracer_end) +{ + // Now we check if we have to update the data that changes monthly + // NOTE: This means that SPA assumes monthly data to update. Not + // any other frequency. + const auto month = ts.get_month() - 1; // Make it 0-based + if (month != time_state.current_month) { + // + const auto tracer_beg = data_tracer_beg.data; + const auto tracer_end = data_tracer_end.data; + const int nvars=data_tracer_end.nvars_; + + // Update the SPA time state information + time_state.current_month = month; + time_state.t_beg_month = util::TimeStamp({ts.get_year(),month+1,1}, {0,0,0}).frac_of_year_in_days(); + time_state.days_this_month = util::days_in_month(ts.get_year(),month+1); + + // Copy spa_end'data into spa_beg'data, and read in the new spa_end + for (int ivar = 0; ivar < nvars ; ++ivar) + { + Kokkos::deep_copy(tracer_beg[ivar], tracer_end[ivar]); + } + // Update the SPA forcing data for this month and next month + // Start by copying next months data to this months data structure. + // NOTE: If the timestep is bigger than monthly this could cause the wrong values + // to be assigned. A timestep greater than a month is very unlikely so we + // will proceed. + int next_month = (time_state.current_month + 1) % 12; + update_tracer_data_from_file(scorpio_reader, ts, + next_month, + tracer_horiz_interp, + data_tracer_end); + } + +} // END updata_spa_timestate + +// This function is based on the SPA::perform_time_interpolation function. + inline void perform_time_interpolation( + const LinozTimeState& time_state, + + const TracerData& data_tracer_beg, + const TracerData& data_tracer_end, + const TracerData& data_tracer_out) +{ + // NOTE: we *assume* data_beg and data_end have the *same* hybrid v coords. + // IF this ever ceases to be the case, you can interp those too. + // Gather time stamp info + auto& t_now = time_state.t_now; + auto& t_beg = time_state.t_beg_month; + auto& delta_t = time_state.days_this_month; + + // We can ||ize over columns as well as over variables and bands + const auto data_beg = data_tracer_beg.data; + const auto data_end = data_tracer_end.data; + const auto data_out = data_tracer_out.data; + const int num_vars = data_tracer_end.nvars_; + + const int ncol = data_beg[0].extent(0); + const int num_vert = data_beg[0].extent(1); + + const int outer_iters = ncol*num_vars; + + const auto policy = ESU::get_default_team_policy(outer_iters, num_vert); + + auto delta_t_fraction = (t_now-t_beg) / delta_t; + + EKAT_REQUIRE_MSG (delta_t_fraction>=0 && delta_t_fraction<=1, + "Error! Convex interpolation with coefficient out of [0,1].\n" + " t_now : " + std::to_string(t_now) + "\n" + " t_beg : " + std::to_string(t_beg) + "\n" + " delta_t: " + std::to_string(delta_t) + "\n"); + + Kokkos::parallel_for("linoz_time_interp_loop", policy, + KOKKOS_LAMBDA(const Team& team) { + + // The policy is over ncols*num_vars, so retrieve icol/ivar + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + // Get column of beg/end/out variable + auto var_beg = ekat::subview(data_beg[ivar],icol); + auto var_end = ekat::subview(data_end[ivar],icol); + auto var_out = ekat::subview(data_out[ivar],icol); + + Kokkos::parallel_for (Kokkos::TeamVectorRange(team,num_vert), + [&] (const int& k) { + var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); + }); + }); + Kokkos::fence(); +} // perform_time_interpolation + +inline void +compute_source_pressure_levels( + const view_1d& ps_src, + const view_2d& p_src, + const const_view_1d& hyam, + const const_view_1d& hybm) +{ + using ExeSpace = typename KT::ExeSpace; + using ESU = ekat::ExeSpaceUtils; + using C = scream::physics::Constants; + + constexpr auto P0 = C::P0; + + const int ncols = ps_src.extent(0); + const int num_vert_packs = p_src.extent(1); + const auto policy = ESU::get_default_team_policy(ncols, num_vert_packs); + + Kokkos::parallel_for("tracer_compute_p_src_loop", policy, + KOKKOS_LAMBDA (const Team& team) { + const int icol = team.league_rank(); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team,num_vert_packs), + [&](const int k) { + p_src(icol,k) = ps_src(icol) * hybm(k) + P0 * hyam(k); + }); + }); +} // compute_source_pressure_levels + +#if 1 +inline void +perform_vertical_interpolation( + const view_2d p_src, + const view_2d p_tgt, + const TracerData& input, + const TracerData& output) +{ + using ExeSpace = typename KT::ExeSpace; + using ESU = ekat::ExeSpaceUtils; + using LIV = ekat::LinInterp; + + // At this stage, begin/end must have the same horiz dimensions + EKAT_REQUIRE(input.ncol_==output.ncol_); + + const int ncols = input.ncol_; + const int nlevs_src = input.nlev_; + const int nlevs_tgt = output.nlev_; + + LIV vert_interp(ncols,nlevs_src,nlevs_tgt); + + // We can ||ize over columns as well as over variables and bands + const int num_vars = input.nvars_; + const int num_vert_packs = nlevs_tgt; + const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); + + // Setup the linear interpolation object + Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + + const int icol = team.league_rank(); + + // Setup + vert_interp.setup(team, ekat::subview(p_src,icol), + ekat::subview(p_tgt,icol)); + }); + Kokkos::fence(); + + // Now use the interpolation object in || over all variables. + const int outer_iters = ncols*num_vars; + const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); + Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const& team) { + + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + const auto x1 = ekat::subview(p_src,icol); + const auto x2 = ekat::subview(p_tgt,icol); + + const auto y1 = ekat::subview(input.data[ivar],icol); + const auto y2 = ekat::subview(output.data[ivar],icol); + + vert_interp.lin_interp(team, x1, x2, y1, y2, icol); + }); + Kokkos::fence(); +} + +#endif } // namespace scream::mam_coupling #endif //EAMXX_MAM_HELPER_MICRO diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 31ee615be43..083a7eb51b1 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -14,7 +14,8 @@ atmosphere_processes: mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc mam4_chlorine_loading_ymd : 20100101 - + mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/RSF_GT200nm_v3.0_c080811.nc + mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/temp_prs_GT200nm_JPL10_c130206.nc grids_manager: Type: Mesh Free geo_data_source: IC_FILE From a3b9f9ddf240acf7ee18ef18200e4f24feaf91c1 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 24 Jul 2024 17:13:21 -0600 Subject: [PATCH 11/31] microphysics - Adding time interpolation for ps and pressure computation. microphysics - Using vertical interpolation. microphysics - Creating advance_tracer_data function call. microphysics - I have commented out the function calls that produce FPEs and added a temporary fix for vertical interpolation. We may need to add one more elevation to the source data. microphysics - Delete linoz reader from *.cpp and *.hpp. --- ...mxx_mam_microphysics_process_interface.cpp | 132 +++++----------- ...mxx_mam_microphysics_process_interface.hpp | 10 +- .../src/physics/mam/impl/helper_micro.hpp | 141 +++++++++++++++--- 3 files changed, 158 insertions(+), 125 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index bb4f21e3da4..21ac97cf1f3 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -167,25 +167,16 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; std::string linoz_file_name = m_params.get("mam4_linoz_file_name"); - linoz_reader_ = create_linoz_data_reader(linoz_file_name, - linoz_params_, ncol_, col_latitudes_, m_comm); - } - std::string my_file="oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc"; - std::string spa_map_file=""; - std::vector var_names{"O3","HO2","NO3","OH"}; - TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names); - TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,my_file); - - // 3 Read in hyam/hybm in start/end data, and pad them - const auto io_grid = TracerHorizInterp_->get_src_grid(); - Field hyam(FieldIdentifier("hyam",io_grid->get_vertical_layout(true),nondim,io_grid->name())); - Field hybm(FieldIdentifier("hybm",io_grid->get_vertical_layout(true),nondim,io_grid->name())); - hyam.allocate_view(); - hybm.allocate_view(); - AtmosphereInput hvcoord_reader(my_file,io_grid,{hyam,hybm},true); - hvcoord_reader.read_variables(); - hvcoord_reader.finalize(); + { + std::string my_file="oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc"; + std::string spa_map_file=""; + std::vector var_names{"O3","HO2","NO3","OH"}; + TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names); + TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,my_file); + tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,my_file); + } + } @@ -283,7 +274,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { const std::string xs_long_file = m_params.get("mam4_xs_long_file"); - photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); + // photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); // FIXME: read relevant land use data from drydep surface file @@ -317,37 +308,10 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] - LinozData_end_.init(ncol_,linoz_params_.nlevs); - LinozData_end_.allocate_data_views(); - - LinozData_start_.init(ncol_,linoz_params_.nlevs); - LinozData_start_.allocate_data_views(); - - LinozData_out_.init(ncol_,linoz_params_.nlevs); - LinozData_out_.allocate_data_views(); - - interpolated_Linoz_data_.init(ncol_,nlev_); - - interpolated_Linoz_data_.set_data_views(linoz_o3_clim, - linoz_o3col_clim, - linoz_t_clim, - linoz_PmL_clim, - linoz_dPmL_dO3, - linoz_dPmL_dT, - linoz_dPmL_dO3col, - linoz_cariolle_pscs); - // Load the first month into spa_end. // Note: At the first time step, the data will be moved into spa_beg, // and spa_end will be reloaded from file with the new month. const int curr_month = timestamp().get_month()-1; // 0-based - linoz_reader_->read_variables(curr_month); - perform_horizontal_interpolation(linoz_params_, LinozData_end_); - - perform_vertical_interpolation(linoz_params_, - dry_atm_.p_mid, - LinozData_end_, - interpolated_Linoz_data_); // const std::string linoz_chlorine_file = "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; // auto ts = timestamp(); @@ -386,9 +350,18 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); tracer_data_beg_.allocate_data_views(); + tracer_data_beg_.allocate_ps(); tracer_data_out_.init(num_cols_io, num_levs_io, nvars); tracer_data_out_.allocate_data_views(); + tracer_data_out_.allocate_ps(); + + p_src_invariant_ = view_2d("pressure_src_invariant",num_cols_io, num_levs_io ); + + for (int ivar = 0; ivar< nvars; ++ivar) { + cnst_offline_[ivar] = view_2d("cnst_offline_",ncol_, nlev_ ); + } + } @@ -423,55 +396,27 @@ void MAMMicrophysics::run_impl(const double dt) { // allocation perspective auto o3_col_dens = buffer_.scratch[8]; - auto ts = timestamp()+dt; - { - /* Gather time and state information for interpolation */ - - /* Update the LinozTimeState to reflect the current time, note the addition of dt */ - linoz_time_state_.t_now = ts.frac_of_year_in_days(); - /* Update time state and if the month has changed, update the data.*/ - update_linoz_timestate(ts, - linoz_time_state_, - linoz_reader_, - linoz_params_, - LinozData_start_, - LinozData_end_); - - perform_time_interpolation(linoz_time_state_, - LinozData_start_, - LinozData_end_, - LinozData_out_); - - perform_vertical_interpolation(linoz_params_, - dry_atm_.p_mid, - LinozData_out_, - interpolated_Linoz_data_); + /* Gather time and state information for interpolation */ + const auto ts = timestamp()+dt; - } const Real chlorine_loading = scream::mam_coupling::chlorine_loading_advance(ts, chlorine_values_, chlorine_time_secs_); - { - /* Gather time and state information for interpolation */ - auto ts = timestamp()+dt; - /* Update the SPATimeState to reflect the current time, note the addition of dt */ + + // /* Update the LinozTimeState to reflect the current time, note the addition of dt */ linoz_time_state_.t_now = ts.frac_of_year_in_days(); - /* Update time state and if the month has changed, update the data.*/ - scream::mam_coupling::update_tracer_timestate( - TracerDataReader_, - ts, - *TracerHorizInterp_, - linoz_time_state_, - tracer_data_beg_, - tracer_data_end_); - - scream::mam_coupling::perform_time_interpolation( - linoz_time_state_, - tracer_data_beg_, - tracer_data_end_, - tracer_data_out_); - } + scream::mam_coupling::advance_tracer_data(TracerDataReader_, + *TracerHorizInterp_, + ts, + linoz_time_state_, + tracer_data_beg_, + tracer_data_end_, + tracer_data_out_, + p_src_invariant_, + dry_atm_.p_mid, + cnst_offline_); + const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; @@ -521,6 +466,7 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: set views here const auto& work_photo_table_icol = ekat::subview(work_photo_table, icol); // set work view using 1D photo_work_arrays_icol + mam4::mo_photo::set_photo_table_work_arrays(photo_table, work_photo_table_icol, photo_work_arrays_icol); @@ -532,11 +478,11 @@ void MAMMicrophysics::run_impl(const double dt) { Real surf_albedo = 0.0; // FIXME: surface albedo Real esfact = 0.0; // FIXME: earth-sun distance factor const auto& photo_rates_icol = ekat::subview(photo_rates, icol); - +#if 0 mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, atm.hydrostatic_dp, atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, lwc_icol, atm.cloud_fraction, esfact, photo_table, photo_work_arrays_icol); - +#endif // compute external forcings at time t(n+1) [molecules/cm^3/s] constexpr int extcnt = mam4::gas_chemistry::extcnt; view_2d extfrc; // FIXME: where to allocate? (nlev, extcnt) @@ -648,6 +594,7 @@ void MAMMicrophysics::run_impl(const double dt) { Real rlats = col_lat * M_PI / 180.0; // convert column latitude to radians int o3_ndx = 0; // index of "O3" in solsym array (in EAM) +#if 0 mam4::lin_strat_chem::lin_strat_chem_solve_kk(o3_col_dens_i(k), temp, zenith_angle, pmid, dt, rlats, linoz_o3_clim(icol, k), linoz_t_clim(icol, k), linoz_o3col_clim(icol, k), @@ -656,7 +603,7 @@ void MAMMicrophysics::run_impl(const double dt) { chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees); - +#endif // update source terms above the ozone decay threshold if (k > nlev - config.linoz.o3_lbl - 1) { Real do3_mass; // diagnostic, not needed @@ -688,7 +635,6 @@ void MAMMicrophysics::run_impl(const double dt) { } void MAMMicrophysics::finalize_impl() { - // linoz_reader_->finalize(); } } // namespace scream diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 4c12576af42..57c38e7e6f4 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -229,15 +229,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // sets defaults for "namelist parameters" void set_defaults_(); - std::shared_ptr linoz_reader_; - mam_coupling::LinozReaderParams linoz_params_; - mam_coupling::view_1d col_latitudes_copy_; mam_coupling::LinozTimeState linoz_time_state_; - mam_coupling::LinozData LinozData_start_; - mam_coupling::LinozData LinozData_end_; - mam_coupling::LinozData LinozData_out_; - mam_coupling::LinozData interpolated_Linoz_data_; - // view_2d work_photo_table_; std::vector chlorine_values_; std::vector chlorine_time_secs_; @@ -249,6 +241,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::TracerData tracer_data_end_; mam_coupling::TracerData tracer_data_beg_; mam_coupling::TracerData tracer_data_out_; + view_2d p_src_invariant_; + view_2d cnst_offline_[4]; diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 617067e1d45..80a08f8d681 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -85,6 +85,8 @@ namespace scream::mam_coupling { int nvars_{-1}; view_2d data[MAX_NVARS_TRACER]; view_1d ps; + const_view_1d hyam; + const_view_1d hybm; void allocate_data_views() { @@ -100,6 +102,8 @@ namespace scream::mam_coupling { void allocate_ps() { + EKAT_REQUIRE_MSG (ncol_ != int(-1), + "Error! ncols has not been set. \n"); ps = view_1d("ps",ncol_); } @@ -117,6 +121,23 @@ namespace scream::mam_coupling { ps = ps_in; } + void set_hyam_n_hybm(const std::shared_ptr& horiz_remapper, + const std::string& tracer_file_name) + { + + // Read in hyam/hybm in start/end data, and pad them + auto nondim = ekat::units::Units::nondimensional(); + const auto io_grid = horiz_remapper->get_src_grid(); + Field hyam_f(FieldIdentifier("hyam",io_grid->get_vertical_layout(true),nondim,io_grid->name())); + Field hybm_f(FieldIdentifier("hybm",io_grid->get_vertical_layout(true),nondim,io_grid->name())); + hyam_f.allocate_view(); + hybm_f.allocate_view(); + AtmosphereInput hvcoord_reader(tracer_file_name,io_grid,{hyam_f,hybm_f},true); + hvcoord_reader.read_variables(); + hvcoord_reader.finalize(); + hyam = hyam_f.get_view(); + hybm = hyam_f.get_view(); + } }; struct LinozData { @@ -143,8 +164,9 @@ namespace scream::mam_coupling { "Error! nlevs has not been set. \n"); for (int ivar = 0; ivar< nvars_; ++ivar) { - data[ivar] = view_2d("linoz_1",ncol_,nlev_); + data[ivar] = view_2d("data_tracer",ncol_,nlev_); } + } //allocate_data_views void set_data_views(std::vector& list_of_views) @@ -670,14 +692,16 @@ update_tracer_data_from_file( tracer_horiz_interp.remap(/*forward = */ true); // const int nvars =tracer_data.nvars_; - // Recall, the fields are registered in the order: ps, ccn3, g_sw, ssa_sw, tau_sw, tau_lw - // 3. Copy from the tgt field of the remapper into the spa_data -// auto ps = tracer_horiz_interp.get_tgt_field (0).get_view(); + // for (int i = 0; i < nvars; ++i) { tracer_data.data[i] = tracer_horiz_interp.get_tgt_field (i).get_view< Real**>(); } + // Recall, the fields are registered in the order: tracers, ps + // 3. Copy from the tgt field of the remapper into the spa_data + tracer_data.ps = tracer_horiz_interp.get_tgt_field(nvars).get_view< Real*>(); + } // update_tracer_data_from_file inline void update_tracer_timestate( @@ -725,7 +749,6 @@ update_tracer_timestate( // This function is based on the SPA::perform_time_interpolation function. inline void perform_time_interpolation( const LinozTimeState& time_state, - const TracerData& data_tracer_beg, const TracerData& data_tracer_end, const TracerData& data_tracer_out) @@ -741,10 +764,16 @@ update_tracer_timestate( const auto data_beg = data_tracer_beg.data; const auto data_end = data_tracer_end.data; const auto data_out = data_tracer_out.data; - const int num_vars = data_tracer_end.nvars_; - const int ncol = data_beg[0].extent(0); - const int num_vert = data_beg[0].extent(1); + + const auto ps_beg = data_tracer_beg.ps; + const auto ps_end = data_tracer_end.ps; + const auto ps_out = data_tracer_out.ps; + + const int num_vars = data_tracer_end.nvars_; + + const int ncol = data_tracer_beg.ncol_; + const int num_vert = data_tracer_beg.nlev_; const int outer_iters = ncol*num_vars; @@ -766,14 +795,19 @@ update_tracer_timestate( const int ivar = team.league_rank() % num_vars; // Get column of beg/end/out variable - auto var_beg = ekat::subview(data_beg[ivar],icol); - auto var_end = ekat::subview(data_end[ivar],icol); - auto var_out = ekat::subview(data_out[ivar],icol); + auto var_beg = ekat::subview(data_beg[ivar],icol); + auto var_end = ekat::subview(data_end[ivar],icol); + auto var_out = ekat::subview(data_out[ivar],icol); Kokkos::parallel_for (Kokkos::TeamVectorRange(team,num_vert), [&] (const int& k) { var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); }); + + if(ivar==1){ + ps_out(icol) = linear_interp(ps_beg(icol), ps_end(icol),delta_t_fraction); + } + }); Kokkos::fence(); } // perform_time_interpolation @@ -790,7 +824,6 @@ compute_source_pressure_levels( using C = scream::physics::Constants; constexpr auto P0 = C::P0; - const int ncols = ps_src.extent(0); const int num_vert_packs = p_src.extent(1); const auto policy = ESU::get_default_team_policy(ncols, num_vert_packs); @@ -805,24 +838,39 @@ compute_source_pressure_levels( }); } // compute_source_pressure_levels -#if 1 + inline void perform_vertical_interpolation( - const view_2d p_src, - const view_2d p_tgt, + const view_2d& p_src_c, + const const_view_2d& p_tgt_c, const TracerData& input, - const TracerData& output) + const view_2d output[]) { using ExeSpace = typename KT::ExeSpace; using ESU = ekat::ExeSpaceUtils; using LIV = ekat::LinInterp; // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_==output.ncol_); + EKAT_REQUIRE(input.ncol_==output[0].extent(0)); +#if 1 + // FIXME: I was encountering a compilation error when using const_view_2d. + // The issue is fixed by https://github.com/E3SM-Project/EKAT/pull/346. + // I will keep this code until this PR is merged into the EKAT master branch and + // we update the EKAT version in our code. + // I am converting const_view_2d to view_2d. + auto p_src_ptr = (Real *)p_src_c.data(); + view_2d p_src(p_src_ptr,p_src_c.extent(0),p_src_c.extent(1)); + auto p_tgt_ptr = (Real *)p_tgt_c.data(); + view_2d p_tgt(p_tgt_ptr,p_tgt_c.extent(0),p_tgt_c.extent(1)); +#else + const auto p_src = p_src_c; + const auto p_tgt = p_tgt_c; +#endif const int ncols = input.ncol_; - const int nlevs_src = input.nlev_; - const int nlevs_tgt = output.nlev_; + // FIXME: I am getting FPEs if I do not subtract 1 from nlevs_src. + const int nlevs_src = input.nlev_-1; + const int nlevs_tgt = output[0].extent(1); LIV vert_interp(ncols,nlevs_src,nlevs_tgt); @@ -832,11 +880,10 @@ perform_vertical_interpolation( const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); // Setup the linear interpolation object - Kokkos::parallel_for("spa_vert_interp_setup_loop", policy_setup, + Kokkos::parallel_for("tracer_vert_interp_setup_loop", policy_setup, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { const int icol = team.league_rank(); - // Setup vert_interp.setup(team, ekat::subview(p_src,icol), ekat::subview(p_tgt,icol)); @@ -846,7 +893,7 @@ perform_vertical_interpolation( // Now use the interpolation object in || over all variables. const int outer_iters = ncols*num_vars; const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); - Kokkos::parallel_for("spa_vert_interp_loop", policy_interp, + Kokkos::parallel_for("tracer_vert_interp_loop", policy_interp, KOKKOS_LAMBDA(typename LIV::MemberType const& team) { const int icol = team.league_rank() / num_vars; @@ -856,13 +903,59 @@ perform_vertical_interpolation( const auto x2 = ekat::subview(p_tgt,icol); const auto y1 = ekat::subview(input.data[ivar],icol); - const auto y2 = ekat::subview(output.data[ivar],icol); + const auto y2 = ekat::subview(output[ivar],icol); vert_interp.lin_interp(team, x1, x2, y1, y2, icol); }); Kokkos::fence(); } -#endif +inline void +advance_tracer_data(std::shared_ptr& scorpio_reader, + AbstractRemapper& tracer_horiz_interp, + const util::TimeStamp& ts, + LinozTimeState& time_state, + TracerData& data_tracer_beg, + TracerData& data_tracer_end, + TracerData& data_tracer_out, + const view_2d& p_src, + const const_view_2d& p_tgt, + const view_2d output[]) +{ + + /* Update the TracerTimeState to reflect the current time, note the addition of dt */ + time_state.t_now = ts.frac_of_year_in_days(); + /* Update time state and if the month has changed, update the data.*/ + update_tracer_timestate( + scorpio_reader, + ts, + tracer_horiz_interp, + time_state, + data_tracer_beg, + data_tracer_end); + // Step 1. Perform time interpolation + perform_time_interpolation( + time_state, + data_tracer_beg, + data_tracer_end, + data_tracer_out); + // Step 2. Compute source pressure levels + compute_source_pressure_levels( + data_tracer_out.ps, + p_src, + data_tracer_out.hyam, + data_tracer_out.hybm); + + // Step 3. Perform vertical interpolation + + perform_vertical_interpolation( + p_src, + p_tgt, + data_tracer_out, + output); + +}// advance_tracer_data + + } // namespace scream::mam_coupling #endif //EAMXX_MAM_HELPER_MICRO From 13c23c1fc46a19a8c1e04630d24cf9b4091e18ca Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 26 Jul 2024 10:56:28 -0600 Subject: [PATCH 12/31] microphysics - Reading and interpolating Linoz files using the tracer reader. Since the Linoz file does not have a PS variable, I am adding a has_ps boolean. --- ...mxx_mam_microphysics_process_interface.cpp | 112 ++++++++++++++---- ...mxx_mam_microphysics_process_interface.hpp | 11 ++ .../src/physics/mam/impl/helper_micro.hpp | 47 ++++++-- 3 files changed, 139 insertions(+), 31 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 21ac97cf1f3..ea782df9ca6 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -164,17 +164,35 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // Creating a Linoz reader and setting Linoz parameters involves reading data from a file // and configuring the necessary parameters for the Linoz model. { - // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; - std::string linoz_file_name = + // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + std::string linoz_file_name = m_params.get("mam4_linoz_file_name"); + std::string spa_map_file=""; + std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col","cariolle_pscs"}; + bool has_ps=false; + LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,linoz_file_name,spa_map_file, var_names, has_ps); + LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader(LinozHorizInterp_,linoz_file_name); + linoz_data_out_.set_has_ps(has_ps); + if (has_ps) { + linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_,linoz_file_name); + } + linoz_data_beg_.set_has_ps(has_ps); + linoz_data_end_.set_has_ps(has_ps); + } { std::string my_file="oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc"; std::string spa_map_file=""; std::vector var_names{"O3","HO2","NO3","OH"}; - TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names); + bool has_ps=false; + TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names, has_ps); TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,my_file); - tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,my_file); + tracer_data_out_.set_has_ps(has_ps); + if (has_ps) { + tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,my_file); + } + tracer_data_beg_.set_has_ps(has_ps); + tracer_data_end_.set_has_ps(has_ps); } @@ -339,29 +357,59 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // Note: At the first time step, the data will be moved into spa_beg, // and spa_end will be reloaded from file with the new month. const int curr_month = timestamp().get_month()-1; // 0-based - const int nvars = 4; - const auto io_grid = TracerHorizInterp_->get_src_grid(); - const int num_cols_io = io_grid->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io = io_grid->get_num_vertical_levels(); // Number of levels per column - tracer_data_end_.init(num_cols_io, num_levs_io, nvars); - scream::mam_coupling::update_tracer_data_from_file(TracerDataReader_, - timestamp(),curr_month, *TracerHorizInterp_, tracer_data_end_); + { + const int nvars = 4; + const auto io_grid = TracerHorizInterp_->get_src_grid(); + const int num_cols_io = io_grid->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io = io_grid->get_num_vertical_levels(); // Number of levels per column + + tracer_data_end_.init(num_cols_io, num_levs_io, nvars); + scream::mam_coupling::update_tracer_data_from_file(TracerDataReader_, + timestamp(),curr_month, *TracerHorizInterp_, tracer_data_end_); - tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); - tracer_data_beg_.allocate_data_views(); - tracer_data_beg_.allocate_ps(); + tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); + tracer_data_beg_.allocate_data_views(); + tracer_data_beg_.allocate_ps(); - tracer_data_out_.init(num_cols_io, num_levs_io, nvars); - tracer_data_out_.allocate_data_views(); - tracer_data_out_.allocate_ps(); + tracer_data_out_.init(num_cols_io, num_levs_io, nvars); + tracer_data_out_.allocate_data_views(); + tracer_data_out_.allocate_ps(); - p_src_invariant_ = view_2d("pressure_src_invariant",num_cols_io, num_levs_io ); + p_src_invariant_ = view_2d("pressure_src_invariant",num_cols_io, num_levs_io ); - for (int ivar = 0; ivar< nvars; ++ivar) { - cnst_offline_[ivar] = view_2d("cnst_offline_",ncol_, nlev_ ); + for (int ivar = 0; ivar< nvars; ++ivar) { + cnst_offline_[ivar] = view_2d("cnst_offline_",ncol_, nlev_ ); + } } +#if 1 + // linoz reader + { + const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); + const int num_cols_io_linoz = io_grid_linoz->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_linoz = io_grid_linoz->get_num_vertical_levels(); // Number of levels per column + const int nvars = 8; + linoz_data_end_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); + scream::mam_coupling::update_tracer_data_from_file(LinozDataReader_, + timestamp(),curr_month, *LinozHorizInterp_, linoz_data_end_); + + linoz_data_beg_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); + linoz_data_beg_.allocate_data_views(); + if (linoz_data_beg_.has_ps){ + linoz_data_beg_.allocate_ps(); + } + + + linoz_data_out_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); + linoz_data_out_.allocate_data_views(); + if (linoz_data_out_.has_ps) + { + linoz_data_out_.allocate_ps(); + } + p_src_linoz_ = view_2d("pressure_src_invariant",num_cols_io_linoz, num_levs_io_linoz ); + } +#endif } @@ -392,6 +440,18 @@ void MAMMicrophysics::run_impl(const double dt) { auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + view_2d linoz_output[8]; + linoz_output[0] = linoz_o3_clim; + linoz_output[1] = linoz_o3col_clim; + linoz_output[2] = linoz_t_clim; + linoz_output[3] = linoz_PmL_clim; + linoz_output[4] = linoz_dPmL_dO3; + linoz_output[5] = linoz_dPmL_dT; + linoz_output[6] = linoz_dPmL_dO3col; + linoz_output[7] = linoz_cariolle_pscs; + + + // it's a bit wasteful to store this for all columns, but simpler from an // allocation perspective auto o3_col_dens = buffer_.scratch[8]; @@ -417,6 +477,18 @@ void MAMMicrophysics::run_impl(const double dt) { dry_atm_.p_mid, cnst_offline_); + // + scream::mam_coupling::advance_tracer_data(LinozDataReader_, + *LinozHorizInterp_, + ts, + linoz_time_state_, + linoz_data_beg_, + linoz_data_end_, + linoz_data_out_, + p_src_linoz_, + dry_atm_.p_mid, + linoz_output); + const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 57c38e7e6f4..9f910f6ff0c 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -236,6 +236,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_3d photo_rates_; view_2d lwc_; + // invariants members std::shared_ptr TracerDataReader_; std::shared_ptr TracerHorizInterp_; mam_coupling::TracerData tracer_data_end_; @@ -244,6 +245,16 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_2d p_src_invariant_; view_2d cnst_offline_[4]; + // linoz reader + std::shared_ptr LinozDataReader_; + std::shared_ptr LinozHorizInterp_; + mam_coupling::TracerData linoz_data_end_; + mam_coupling::TracerData linoz_data_beg_; + mam_coupling::TracerData linoz_data_out_; + view_2d p_src_linoz_; + + + }; // MAMMicrophysics diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 80a08f8d681..49ae8c2648b 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -22,7 +22,7 @@ namespace scream::mam_coupling { using view_1d_host = typename KT::view_1d::HostMirror; constexpr int NVARS_LINOZ=8; - constexpr int MAX_NVARS_TRACER=5; + constexpr int MAX_NVARS_TRACER=10; const std::vector linoz_var_names={"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", @@ -87,6 +87,7 @@ namespace scream::mam_coupling { view_1d ps; const_view_1d hyam; const_view_1d hybm; + bool has_ps{false}; void allocate_data_views() { @@ -100,10 +101,18 @@ namespace scream::mam_coupling { } } //allocate_data_views + void set_has_ps(const bool has_ps_in) + { + has_ps=has_ps_in; + } + void allocate_ps() { EKAT_REQUIRE_MSG (ncol_ != int(-1), "Error! ncols has not been set. \n"); + EKAT_REQUIRE_MSG (has_ps, + "Error! file does have the PS variable. \n"); + ps = view_1d("ps",ncol_); } @@ -118,12 +127,17 @@ namespace scream::mam_coupling { void set_data_ps(const view_1d& ps_in) { + EKAT_REQUIRE_MSG (has_ps, + "Error! file does have the PS variable. \n"); ps = ps_in; } void set_hyam_n_hybm(const std::shared_ptr& horiz_remapper, const std::string& tracer_file_name) { + std::cout << has_ps<<" has_ps" << "\n"; + EKAT_REQUIRE_MSG (has_ps, + "Error! file does have the PS variable. \n"); // Read in hyam/hybm in start/end data, and pad them auto nondim = ekat::units::Units::nondimensional(); @@ -609,7 +623,8 @@ create_horiz_remapper ( const std::shared_ptr& model_grid, const std::string& trace_data_file, const std::string& map_file, - std::vector& var_names + const std::vector& var_names, + bool& has_ps ) { using namespace ShortFieldTagsNames; @@ -617,6 +632,7 @@ create_horiz_remapper ( scorpio::register_file(trace_data_file,scorpio::Read); const int nlevs_data = scorpio::get_dimlen(trace_data_file,"lev"); const int ncols_data = scorpio::get_dimlen(trace_data_file,"ncol"); + has_ps = scorpio::has_var(trace_data_file,"PS"); scorpio::release_file(trace_data_file); // We could use model_grid directly if using same num levels, @@ -652,12 +668,15 @@ create_horiz_remapper ( ifield.allocate_view(); remapper->register_field_from_tgt (ifield); } + // zonal files do not have the PS variable. + if (has_ps) + { + Field ps (FieldIdentifier("PS", layout_2d, nondim,tgt_grid->name())); + ps.allocate_view(); + remapper->register_field_from_tgt (ps); - Field ps (FieldIdentifier("PS", layout_2d, nondim,tgt_grid->name())); - ps.allocate_view(); - remapper->register_field_from_tgt (ps); + } remapper->registration_ends(); - return remapper; } // create_horiz_remapper @@ -666,8 +685,8 @@ inline std::shared_ptr create_tracer_data_reader ( - const std::shared_ptr& horiz_remapper, - const std::string& tracer_data_file) + const std::shared_ptr& horiz_remapper, + const std::string& tracer_data_file) { std::vector io_fields; for (int i=0; iget_num_fields(); ++i) { @@ -698,9 +717,11 @@ update_tracer_data_from_file( tracer_data.data[i] = tracer_horiz_interp.get_tgt_field (i).get_view< Real**>(); } - // Recall, the fields are registered in the order: tracers, ps - // 3. Copy from the tgt field of the remapper into the spa_data - tracer_data.ps = tracer_horiz_interp.get_tgt_field(nvars).get_view< Real*>(); + if (tracer_data.has_ps) { + // Recall, the fields are registered in the order: tracers, ps + // 3. Copy from the tgt field of the remapper into the spa_data + tracer_data.ps = tracer_horiz_interp.get_tgt_field(nvars).get_view< Real*>(); + } } // update_tracer_data_from_file inline void @@ -939,6 +960,7 @@ advance_tracer_data(std::shared_ptr& scorpio_reader, data_tracer_beg, data_tracer_end, data_tracer_out); + if (data_tracer_out.has_ps){ // Step 2. Compute source pressure levels compute_source_pressure_levels( data_tracer_out.ps, @@ -946,6 +968,9 @@ advance_tracer_data(std::shared_ptr& scorpio_reader, data_tracer_out.hyam, data_tracer_out.hybm); + } + + // Step 3. Perform vertical interpolation perform_vertical_interpolation( From f1dc9e9c95f0d2ee07d3fa07ecb1e48b7fd9540d Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 26 Jul 2024 11:53:57 -0600 Subject: [PATCH 13/31] microphysics - Computing source pressure for zonal files (Linoz files) microphysics - The Linoz reader is working, i.e., at least it is not producing FPEs. microphysics - Getting file names from namelist. microphysics - Adding tracer file type. microphysics - Tracer reader is working for vertical emission, but we need to perform verification. microphysics - reader for vertical emission using two files. microphysics - Getting file name of vertical emission from namelist. fix issue in copying progs related to cloudborne aerosols, assign lwc and cldnum --- ...mxx_mam_microphysics_process_interface.cpp | 232 +++++-- ...mxx_mam_microphysics_process_interface.hpp | 17 +- .../src/physics/mam/impl/helper_micro.hpp | 650 +++++++----------- .../mam/aero_microphys/input.yaml | 5 +- 4 files changed, 441 insertions(+), 463 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index ea782df9ca6..955a07f311e 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -140,17 +140,35 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ add_tracer("qc", grid_, kg/kg); // cloud liquid wet mixing ratio add_tracer("nc", grid_, n_unit); // cloud liquid wet number mixing ratio - // (interstitial) aerosol tracers of interest: mass (q) and number (n) mixing ratios + // interstitial and cloudborne aerosol tracers of interest: mass (q) and + // number (n) mixing ratios for (int m = 0; m < mam_coupling::num_aero_modes(); ++m) { + // interstitial aerosol tracers of interest: number (n) mixing ratios const char* int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); - add_tracer(int_nmr_field_name, grid_, n_unit); + add_field(int_nmr_field_name, scalar3d_layout_mid, n_unit, grid_name, "tracers"); + + // cloudborne aerosol tracers of interest: number (n) mixing ratios + // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are + // NOT advected + const char* cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); + add_field(cld_nmr_field_name, scalar3d_layout_mid, n_unit, grid_name); + for (int a = 0; a < mam_coupling::num_aero_species(); ++a) { + // (interstitial) aerosol tracers of interest: mass (q) mixing ratios const char* int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); if (strlen(int_mmr_field_name) > 0) { add_tracer(int_mmr_field_name, grid_, kg/kg); } - } - } + + // (cloudborne) aerosol tracers of interest: mass (q) mixing ratios + // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are + // NOT advected + const char* cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(m, a); + if (strlen(cld_mmr_field_name) > 0) { + add_field(cld_mmr_field_name, scalar3d_layout_mid, kg/kg, grid_name); + } + } // end for loop for num species + } // end for loop for num modes // aerosol-related gases: mass mixing ratios for (int g = 0; g < mam_coupling::num_aero_gases(); ++g) { @@ -165,36 +183,62 @@ void MAMMicrophysics::set_grids(const std::shared_ptr grids_ // and configuring the necessary parameters for the Linoz model. { // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; - std::string linoz_file_name = + linoz_file_name_ = m_params.get("mam4_linoz_file_name"); std::string spa_map_file=""; std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col","cariolle_pscs"}; - bool has_ps=false; - LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,linoz_file_name,spa_map_file, var_names, has_ps); - LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader(LinozHorizInterp_,linoz_file_name); - linoz_data_out_.set_has_ps(has_ps); - if (has_ps) { - linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_,linoz_file_name); + TracerFileType tracer_file_type; + LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,linoz_file_name_,spa_map_file, var_names, tracer_file_type); + LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader(LinozHorizInterp_,linoz_file_name_); + linoz_data_out_.set_file_type(tracer_file_type); + if (tracer_file_type == TracerFileType::FORMULA_PS) { + linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_,linoz_file_name_); } - linoz_data_beg_.set_has_ps(has_ps); - linoz_data_end_.set_has_ps(has_ps); + linoz_data_beg_.set_file_type(tracer_file_type); + linoz_data_end_.set_file_type(tracer_file_type); } { - std::string my_file="oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc"; + oxid_file_name_ = + m_params.get("mam4_oxid_file_name"); std::string spa_map_file=""; std::vector var_names{"O3","HO2","NO3","OH"}; - bool has_ps=false; - TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,my_file,spa_map_file, var_names, has_ps); - TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,my_file); - tracer_data_out_.set_has_ps(has_ps); - if (has_ps) { - tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,my_file); + TracerFileType tracer_file_type; + TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,oxid_file_name_, + spa_map_file, var_names, tracer_file_type); + TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,oxid_file_name_); + tracer_data_out_.set_file_type(tracer_file_type); + if (tracer_file_type == TracerFileType::FORMULA_PS) { + tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,oxid_file_name_); } - tracer_data_beg_.set_has_ps(has_ps); - tracer_data_end_.set_has_ps(has_ps); + tracer_data_beg_.set_file_type(tracer_file_type); + tracer_data_end_.set_file_type(tracer_file_type); } + { + vert_emis_file_name_= "cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc"; + std::string spa_map_file=""; + std::vector var_names{"BB"}; + + for (auto file_name :vert_emis_file_name_) + { + TracerFileType tracer_file_type; + auto hor_rem = scream::mam_coupling::create_horiz_remapper(grid_,file_name, + spa_map_file, var_names, tracer_file_type); + auto file_reader = scream::mam_coupling::create_tracer_data_reader(hor_rem, + file_name); + scream::mam_coupling::TracerData data_out, data_beg, data_end; + data_out.set_file_type(tracer_file_type); + data_beg.set_file_type(tracer_file_type); + data_end.set_file_type(tracer_file_type); + + VertEmissionsHorizInterp_.push_back(hor_rem); + VertEmissionsDataReader_.push_back(file_reader); + vert_emis_data_out_.push_back(data_out); + vert_emis_data_beg_.push_back(data_beg); + vert_emis_data_end_.push_back(data_end); + } + } } @@ -265,17 +309,33 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { if (run_type==RunType::Initial) { } - // set wet/dry aerosol state data (interstitial aerosols only) + // interstitial and cloudborne aerosol tracers of interest: mass (q) and + // number (n) mixing ratios for (int m = 0; m < mam_coupling::num_aero_modes(); ++m) { + // interstitial aerosol tracers of interest: number (n) mixing ratios const char* int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); wet_aero_.int_aero_nmr[m] = get_field_out(int_nmr_field_name).get_view(); dry_aero_.int_aero_nmr[m] = buffer_.dry_int_aero_nmr[m]; + + // cloudborne aerosol tracers of interest: number (n) mixing ratios + const char* cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); + wet_aero_.cld_aero_nmr[m] = get_field_out(cld_nmr_field_name).get_view(); + dry_aero_.cld_aero_nmr[m] = buffer_.dry_cld_aero_nmr[m]; + for (int a = 0; a < mam_coupling::num_aero_species(); ++a) { + // (interstitial) aerosol tracers of interest: mass (q) mixing ratios const char* int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); if (strlen(int_mmr_field_name) > 0) { wet_aero_.int_aero_mmr[m][a] = get_field_out(int_mmr_field_name).get_view(); dry_aero_.int_aero_mmr[m][a] = buffer_.dry_int_aero_mmr[m][a]; } + + // (cloudborne) aerosol tracers of interest: mass (q) mixing ratios + const char* cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(m, a); + if(strlen(cld_mmr_field_name) > 0) { + wet_aero_.cld_aero_mmr[m][a] = get_field_out(cld_mmr_field_name).get_view(); + dry_aero_.cld_aero_mmr[m][a] = buffer_.dry_cld_aero_mmr[m][a]; + } } } @@ -343,8 +403,8 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { chlorine_values_, chlorine_time_secs_ ); } - const int photo_table_len = get_photo_table_work_len(photo_table_); - work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); + // const int photo_table_len = get_photo_table_work_len(photo_table_); + // work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); // here's where we store per-column photolysis rates photo_rates_ = view_3d("photo_rates", ncol_, nlev_, mam4::mo_photo::phtcnt); @@ -382,7 +442,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { cnst_offline_[ivar] = view_2d("cnst_offline_",ncol_, nlev_ ); } } -#if 1 + // linoz reader { const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); @@ -395,21 +455,59 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { linoz_data_beg_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_beg_.allocate_data_views(); - if (linoz_data_beg_.has_ps){ + if (linoz_data_beg_.file_type == TracerFileType::FORMULA_PS){ linoz_data_beg_.allocate_ps(); } - linoz_data_out_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_out_.allocate_data_views(); - if (linoz_data_out_.has_ps) + if (linoz_data_out_.file_type == TracerFileType::FORMULA_PS) { linoz_data_out_.allocate_ps(); } + else if (linoz_data_out_.file_type == TracerFileType::ZONAL) { + // we use ncremap and python scripts to convert zonal files to ne4pn4 grids. + p_src_linoz_ = view_2d("pressure_src_invariant",ncol_, num_levs_io_linoz ); + scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_,p_src_linoz_); + } + } + + // vertical emissions + { + // FIXME: I am assuming 1 variable per file + const int nvars = 1; + for (size_t i = 0; i < vert_emis_file_name_.size(); ++i) + { + const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); + const int num_cols_io_emis = io_grid_emis->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_emis = io_grid_emis->get_num_vertical_levels(); // Number of levels per column + vert_emis_data_end_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); + scream::mam_coupling::update_tracer_data_from_file(VertEmissionsDataReader_[i], + timestamp(),curr_month, *VertEmissionsHorizInterp_[i], vert_emis_data_end_[i]); + + vert_emis_data_beg_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); + vert_emis_data_beg_[i].allocate_data_views(); + if (vert_emis_data_beg_[i].file_type == TracerFileType::FORMULA_PS){ + vert_emis_data_beg_[i].allocate_ps(); + } - p_src_linoz_ = view_2d("pressure_src_invariant",num_cols_io_linoz, num_levs_io_linoz ); + vert_emis_data_out_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); + vert_emis_data_out_[i].allocate_data_views(); + if (vert_emis_data_out_[i].file_type == TracerFileType::FORMULA_PS) + { + vert_emis_data_out_[i].allocate_ps(); + } else if (vert_emis_data_out_[i].file_type == TracerFileType::VERT_EMISSION) { + auto zi_src = scream::mam_coupling::get_altitude_int(VertEmissionsHorizInterp_[i], + vert_emis_file_name_[i]); + vert_emis_altitude_int_.push_back(zi_src); + } + + const auto emis_output = view_2d("vert_emis_output_",num_cols_io_emis, num_levs_io_emis ); + vert_emis_output_.push_back(emis_output); + + + }// end i } -#endif } @@ -422,8 +520,6 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::parallel_for("preprocess", scan_policy, preprocess_); Kokkos::fence(); - - // reset internal WSM variables //workspace_mgr_.reset_internals(); @@ -464,8 +560,10 @@ void MAMMicrophysics::run_impl(const double dt) { chlorine_time_secs_); - // /* Update the LinozTimeState to reflect the current time, note the addition of dt */ + // /* Update the TracerTimeState to reflect the current time, note the addition of dt */ linoz_time_state_.t_now = ts.frac_of_year_in_days(); + // FIXME: we do not need altitude_int for invariant tracers and linoz fields. + view_1d dummy_altitude_int; scream::mam_coupling::advance_tracer_data(TracerDataReader_, *TracerHorizInterp_, ts, @@ -475,9 +573,10 @@ void MAMMicrophysics::run_impl(const double dt) { tracer_data_out_, p_src_invariant_, dry_atm_.p_mid, + dummy_altitude_int, + dry_atm_.z_iface, cnst_offline_); - // scream::mam_coupling::advance_tracer_data(LinozDataReader_, *LinozHorizInterp_, ts, @@ -487,9 +586,28 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_data_out_, p_src_linoz_, dry_atm_.p_mid, + dummy_altitude_int, + dry_atm_.z_iface, linoz_output); - + for (size_t i = 0; i < vert_emis_file_name_.size(); ++i) + { + // FIXME: Here I am assuming one output per file. + view_2d vert_emis_output[1]; + vert_emis_output[0] = vert_emis_output_[i]; + scream::mam_coupling::advance_tracer_data(VertEmissionsDataReader_[i], + *VertEmissionsHorizInterp_[i], + ts, + linoz_time_state_, + vert_emis_data_beg_[i], + vert_emis_data_end_[i], + vert_emis_data_out_[i], + p_src_linoz_, + dry_atm_.p_mid, + vert_emis_altitude_int_[i], + dry_atm_.z_iface, + vert_emis_output); + } const_view_1d &col_latitudes = col_latitudes_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; mam_coupling::AerosolState &dry_aero = dry_aero_; @@ -516,43 +634,46 @@ void MAMMicrophysics::run_impl(const double dt) { auto z_iface = ekat::subview(dry_atm.z_iface, icol); Real phis = dry_atm.phis(icol); + // liquid water cloud content + const auto& lwc_icol = ekat::subview(lwc, icol); + Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { + lwc_icol(k) = atm.ice_mixing_ratio(k) + atm.liquid_mixing_ratio(k); + }); + team.team_barrier(); + // set surface state data haero::Surface sfc{}; // fetch column-specific subviews into aerosol prognostics - mam4::Prognostics progs = mam_coupling::interstitial_aerosols_for_column(dry_aero, icol); + mam4::Prognostics progs = mam_coupling::aerosols_for_column(dry_aero, icol); // set up diagnostics mam4::Diagnostics diags(nlev); // calculate o3 column densities (first component of col_dens in Fortran code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); -<<<<<<< HEAD - //impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); -======= // impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); ->>>>>>> microphysics - Adding photo table paths to input file. // set up photolysis work arrays for this column. mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; // FIXME: set views here - const auto& work_photo_table_icol = ekat::subview(work_photo_table, icol); + // const auto& work_photo_table_icol = ekat::subview(work_photo_table, icol); // set work view using 1D photo_work_arrays_icol - mam4::mo_photo::set_photo_table_work_arrays(photo_table, - work_photo_table_icol, - photo_work_arrays_icol); - + // mam4::mo_photo::set_photo_table_work_arrays(photo_table, + // work_photo_table_icol, + // photo_work_arrays_icol); // ... look up photolysis rates from our table // NOTE: the table interpolation operates on an entire column of data, so we // NOTE: must do it before dispatching to individual vertical levels Real zenith_angle = 0.0; // FIXME: need to get this from EAMxx [radians] Real surf_albedo = 0.0; // FIXME: surface albedo Real esfact = 0.0; // FIXME: earth-sun distance factor + const auto& photo_rates_icol = ekat::subview(photo_rates, icol); #if 0 mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, atm.hydrostatic_dp, - atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, lwc_icol, + atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, atm.liquid_mixing_ratio, atm.cloud_fraction, esfact, photo_table, photo_work_arrays_icol); #endif // compute external forcings at time t(n+1) [molecules/cm^3/s] @@ -577,18 +698,20 @@ void MAMMicrophysics::run_impl(const double dt) { Real pblh = atm.planetary_boundary_layer_height; Real qv = atm.vapor_mixing_ratio(k); Real cldfrac = atm.cloud_fraction(k); + Real lwc = atm.liquid_mixing_ratio(k); + Real cldnum = atm.cloud_liquid_number_mixing_ratio(k); // extract aerosol state variables into "working arrays" (mass mixing ratios) // (in EAM, this is done in the gas_phase_chemdr subroutine defined within // mozart/mo_gas_phase_chemdr.F90) Real q[gas_pcnst] = {}; Real qqcw[gas_pcnst] = {}; - //mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); + mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); // convert mass mixing ratios to volume mixing ratios (VMR), equivalent // to tracer mixing ratios (TMR)) Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - //mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); + mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); // aerosol/gas species tendencies (output) Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; @@ -634,8 +757,7 @@ void MAMMicrophysics::run_impl(const double dt) { // (taken from mam4xx setsox validation test) const Real mbar = haero::Constants::molec_weight_dry_air; constexpr int indexm = 0; // FIXME: index of xhnm in invariants array (??) - Real cldnum = 0.0; // FIXME: droplet number concentration: where do we get this? - /*setsox_single_level(loffset, dt, pmid, pdel, temp, mbar, lwc(k), + /*mam4::mo_setsox::setsox_single_level(loffset, dt, pmid, pdel, temp, mbar, lwc, cldfrac, cldnum, invariants[indexm], config.setsox, vmrcw, vmr);*/ // calculate aerosol water content using water uptake treatment @@ -653,7 +775,7 @@ void MAMMicrophysics::run_impl(const double dt) { zm, pblh, qv, cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a, dgncur_awet, - wetdens, qaerwat);*/ + wetdens, qaerwat); */ //----------------- // LINOZ chemistry @@ -677,11 +799,11 @@ void MAMMicrophysics::run_impl(const double dt) { o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees); #endif // update source terms above the ozone decay threshold - if (k > nlev - config.linoz.o3_lbl - 1) { + /*if (k > nlev - config.linoz.o3_lbl - 1) { Real do3_mass; // diagnostic, not needed mam4::lin_strat_chem::lin_strat_sfcsink_kk(dt, pdel, vmr[o3_ndx], config.linoz.o3_sfc, config.linoz.o3_tau, do3_mass); - } + }*/ // ... check for negative values and reset to zero for (int i = 0; i < gas_pcnst; ++i) { @@ -697,7 +819,7 @@ void MAMMicrophysics::run_impl(const double dt) { // transfer updated prognostics from work arrays //mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); - //mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k);*/ + //mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k); }); }); diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 9f910f6ff0c..074d9b3fd36 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -52,6 +52,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // a thread team dispatched to a single vertical column using ThreadTeam = mam4::ThreadTeam; + using TracerFileType = mam_coupling::TracerFileType; + public: @@ -229,7 +231,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // sets defaults for "namelist parameters" void set_defaults_(); - mam_coupling::LinozTimeState linoz_time_state_; + mam_coupling::TracerTimeState linoz_time_state_; view_2d work_photo_table_; std::vector chlorine_values_; std::vector chlorine_time_secs_; @@ -243,6 +245,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::TracerData tracer_data_beg_; mam_coupling::TracerData tracer_data_out_; view_2d p_src_invariant_; + std::string oxid_file_name_; view_2d cnst_offline_[4]; // linoz reader @@ -252,6 +255,18 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::TracerData linoz_data_beg_; mam_coupling::TracerData linoz_data_out_; view_2d p_src_linoz_; + std::string linoz_file_name_; + + // Vertical emission uses 9 files, here I am using std::vector to stote instance of each file. + std::vector> VertEmissionsDataReader_; + std::vector> VertEmissionsHorizInterp_; + std::vector vert_emis_data_end_; + std::vector vert_emis_data_beg_; + std::vector vert_emis_data_out_; + std::vector vert_emis_file_name_; + std::vector vert_emis_altitude_int_; + std::vector vert_emis_output_; + diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 49ae8c2648b..0215541f71f 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -13,44 +13,30 @@ namespace scream::mam_coupling { using namespace ShortFieldTagsNames; + using view_1d_host = typename KT::view_1d::HostMirror; - // using npack equal to 1. - using LIV = ekat::LinInterp; using ExeSpace = typename KT::ExeSpace; using ESU = ekat::ExeSpaceUtils; + using C = scream::physics::Constants; + using LIV = ekat::LinInterp; - using view_1d_host = typename KT::view_1d::HostMirror; + enum TracerFileType{ + // file with PS ncol, lev, and time + FORMULA_PS, + // nc zonal file from ncremap + ZONAL, + // vertical emission files + VERT_EMISSION, + }; - constexpr int NVARS_LINOZ=8; + /* Maximum number of tracers (or fields) that the tracer reader can handle. + Note: We are not allocating memory for MAX_NVARS_TRACER tracers. + Therefore, if a file contains more than this number, it is acceptable to increase this limit. + Currently, Linoz files have 8 fields. */ constexpr int MAX_NVARS_TRACER=10; - const std::vector - linoz_var_names={"o3_clim", "o3col_clim", - "t_clim", "PmL_clim", "dPmL_dO3", - "dPmL_dT", "dPmL_dO3col","cariolle_pscs"}; - - struct LinozReaderParams { - int nlevs{-1}; - int nlat{-1}; - // latitude array in linoz data. - view_1d latitudes; - // hybrid level pressure at interfaces (1000*(A+B)) - view_1d levs; - - // non_interpolated data from linoz files. - view_2d data_orig[NVARS_LINOZ]; - - // data arrays after horizontal interpolation. - view_2d data_horiz[NVARS_LINOZ]; - - // work arrays - view_int_1d kupper; - // - view_2d pin; - view_1d col_latitudes; - }; // Linoz structures to help manage all of the variables: - struct LinozTimeState { + struct TracerTimeState { // Whether the timestate has been initialized. // The current month int current_month = -1; @@ -62,7 +48,7 @@ namespace scream::mam_coupling { Real t_now; // Number of days in the current month, cast as a Real Real days_this_month; - }; // LinozTimeState + }; // TricerTimeState struct TracerData{ TracerData() = default; @@ -83,11 +69,14 @@ namespace scream::mam_coupling { int ncol_{-1}; int nlev_{-1}; int nvars_{-1}; + // We cannot use a std::vector + // because we need to access these views from device. view_2d data[MAX_NVARS_TRACER]; view_1d ps; const_view_1d hyam; const_view_1d hybm; - bool has_ps{false}; + + TracerFileType file_type; void allocate_data_views() { @@ -101,16 +90,16 @@ namespace scream::mam_coupling { } } //allocate_data_views - void set_has_ps(const bool has_ps_in) + void set_file_type (const TracerFileType file_type_in) { - has_ps=has_ps_in; + file_type=file_type_in; } void allocate_ps() { EKAT_REQUIRE_MSG (ncol_ != int(-1), "Error! ncols has not been set. \n"); - EKAT_REQUIRE_MSG (has_ps, + EKAT_REQUIRE_MSG (file_type == FORMULA_PS, "Error! file does have the PS variable. \n"); ps = view_1d("ps",ncol_); @@ -127,7 +116,7 @@ namespace scream::mam_coupling { void set_data_ps(const view_1d& ps_in) { - EKAT_REQUIRE_MSG (has_ps, + EKAT_REQUIRE_MSG (file_type == FORMULA_PS, "Error! file does have the PS variable. \n"); ps = ps_in; } @@ -135,11 +124,10 @@ namespace scream::mam_coupling { void set_hyam_n_hybm(const std::shared_ptr& horiz_remapper, const std::string& tracer_file_name) { - std::cout << has_ps<<" has_ps" << "\n"; - EKAT_REQUIRE_MSG (has_ps, + EKAT_REQUIRE_MSG (file_type == FORMULA_PS, "Error! file does have the PS variable. \n"); - // Read in hyam/hybm in start/end data, and pad them + // Read in hyam/hybm in start/end data auto nondim = ekat::units::Units::nondimensional(); const auto io_grid = horiz_remapper->get_src_grid(); Field hyam_f(FieldIdentifier("hyam",io_grid->get_vertical_layout(true),nondim,io_grid->name())); @@ -152,224 +140,26 @@ namespace scream::mam_coupling { hyam = hyam_f.get_view(); hybm = hyam_f.get_view(); } - }; - - struct LinozData { - - LinozData() = default; - LinozData(const int ncol, const int nlev) - { - init (ncol,nlev); - } - void init (const int ncol, const int nlev){ - ncol_=ncol; - nlev_=nlev; - } - int ncol_{-1}; - int nlev_{-1}; - int nvars_{NVARS_LINOZ}; - view_2d data[NVARS_LINOZ]; - - void allocate_data_views() - { - EKAT_REQUIRE_MSG (ncol_ != int(-1), - "Error! ncols has not been set. \n"); - EKAT_REQUIRE_MSG (nlev_ !=int(-1), - "Error! nlevs has not been set. \n"); - - for (int ivar = 0; ivar< nvars_; ++ivar) { - data[ivar] = view_2d("data_tracer",ncol_,nlev_); - } - - } //allocate_data_views - - void set_data_views(std::vector& list_of_views) - { - for (int ivar = 0; ivar< nvars_; ++ivar) { - EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, - "Error! Insufficient memory size.\n"); - data[ivar] =list_of_views[ivar]; - } - } - - void set_data_views(const view_2d& linoz_o3_clim, - const view_2d& linoz_o3col_clim, - const view_2d& linoz_t_clim, - const view_2d& linoz_PmL_clim, - const view_2d& linoz_dPmL_dO3, - const view_2d& linoz_dPmL_dT, - const view_2d& linoz_dPmL_dO3col, - const view_2d& linoz_cariolle_pscs) - { - data[0] = linoz_o3_clim; - data[1] = linoz_o3col_clim; - data[2] = linoz_t_clim; - data[3] = linoz_PmL_clim; - data[4] = linoz_dPmL_dO3; - data[5] = linoz_dPmL_dT; - data[6] = linoz_dPmL_dO3col; - data[7] = linoz_cariolle_pscs; - } }; - // define the different field layouts that will be used for this process - inline std::shared_ptr - create_linoz_data_reader ( - const std::string& linoz_data_file, - LinozReaderParams& linoz_params, - const int ncol, - const const_view_1d const_col_latitudes, - const ekat::Comm& comm) + inline + const_view_1d get_altitude_int(const std::shared_ptr& horiz_remapper, + const std::string& tracer_file_name) { + // Read in hyam/hybm in start/end data + auto nondim = ekat::units::Units::nondimensional(); + const auto io_grid = horiz_remapper->get_src_grid(); + Field altitude_int_f(FieldIdentifier("altitude_int",io_grid->get_vertical_layout(false),nondim,io_grid->name())); + altitude_int_f.allocate_view(); + AtmosphereInput hvcoord_reader(tracer_file_name,io_grid,{altitude_int_f},true); + hvcoord_reader.read_variables(); + hvcoord_reader.finalize(); + return altitude_int_f.get_view(); + }// set_altitude_int - auto make_layout = [](const std::vector& extents, - const std::vector& names) - { - std::vector tags(extents.size(),CMP); - return FieldLayout(tags,extents,names); - }; - - // query the file for its resolution - scorpio::register_file(linoz_data_file,scorpio::Read); - const int nlevs_data = scorpio::get_dimlen(linoz_data_file,"lev"); - const int nlat_data = scorpio::get_dimlen(linoz_data_file,"lat"); - scorpio::release_file(linoz_data_file); - linoz_params.nlevs=nlevs_data; - linoz_params.nlat=nlat_data; - - // create an IO grid, with that number of cols - // linoz files do not have number of cols, - // I will use nlat_data instead. - - std::string name="linoz_grid"; - const auto io_grid = std::make_shared(name,nlat_data,nlevs_data,comm); - - const auto nondim = ekat::units::Units::nondimensional(); - - auto scalar2d_layout_linoz = make_layout({nlevs_data, nlat_data}, - {"lev","lat"}); - auto scalar1d_lat_layout_linoz = make_layout({nlat_data}, - {"lat"}); - auto scalar1d_lev_layout_linoz = make_layout({nlevs_data}, - {"lev"}); - - const int nvars=NVARS_LINOZ; - - Field lat (FieldIdentifier("lat", scalar1d_lat_layout_linoz, nondim,io_grid->name())); - Field lev (FieldIdentifier("lev", scalar1d_lev_layout_linoz, nondim,io_grid->name())); - lat.allocate_view(); - lev.allocate_view(); - - std::vector io_fields; - io_fields.push_back(lat); - io_fields.push_back(lev); - - // FIXME: units are wrong. - for (int ivar = 0; ivar < nvars; ++ivar) { - auto var_name = linoz_var_names[ivar]; - // set and allocate fields - Field f(FieldIdentifier(var_name, scalar2d_layout_linoz, nondim,io_grid->name())); - f.allocate_view(); - io_fields.push_back(f); - // get views - linoz_params.data_orig[ivar] = io_fields[ivar+2].get_view(); - // allocate views to store data after horizontal interpolation. - linoz_params.data_horiz[ivar]=view_2d(var_name+"_test", nlevs_data, ncol); - } - - const auto& latitudes_degree = io_fields[0].get_view(); - linoz_params.levs = io_fields[1].get_view< Real*>(); - - // make a copy of col_latitudes without const Real - view_1d col_latitudes("col",ncol); - Kokkos::deep_copy(col_latitudes, const_col_latitudes); - linoz_params.col_latitudes = col_latitudes; - - // allocate temp views - linoz_params.kupper = view_int_1d("kupper",ncol); - linoz_params.pin = view_2d("pin", ncol,nlevs_data); - - const auto& pin = linoz_params.pin; - view_1d latitudes("lat",nlevs_data ); - - const auto& levs = linoz_params.levs; - const auto policy_interp = ESU::get_default_team_policy(ncol, nlevs_data); - const int pi =haero::Constants::pi; - Kokkos::parallel_for("unit_convertion", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - - const int icol = team.league_rank(); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), [&] (const Int& kk) { - // mbar->pascals - pin(icol, kk) = levs(kk)*100; - // radians to degrees - if (icol==0){ - latitudes(kk) = latitudes_degree (kk)* pi/180.; - } - }); - }); - Kokkos::fence(); - linoz_params.latitudes=latitudes; - - return std::make_shared(linoz_data_file, io_grid,io_fields,true); - } - - - inline void perform_horizontal_interpolation( const LinozReaderParams& linoz_params, LinozData& linoz_data_out) - { - // FIXME: get this inputs from eamxx interface. - const auto col_latitudes = linoz_params.col_latitudes; - const int ncol = linoz_data_out.ncol_; - const int linoz_data_nlev = linoz_data_out.nlev_; - const int nvars = linoz_data_out.nvars_; - - // We can ||ize over columns as well as over variables and bands - LIV horiz_interp(linoz_data_nlev, linoz_params.nlat, ncol); - const auto policy_setup = ESU::get_default_team_policy(1, ncol); - auto lat = linoz_params.latitudes; - - Kokkos::parallel_for("vert_interp_setup_loop", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - // Setup - horiz_interp.setup(team, lat, col_latitudes); - }); - Kokkos::fence(); - - // Now use the interpolation object in || over all variables. - const int outer_iters = linoz_params.nlevs; - const auto policy_interp = ESU::get_default_team_policy(outer_iters, ncol); - Kokkos::parallel_for("linoz_horizongal_interp_loop", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int kk = team.league_rank(); - const auto x1 = lat; - const auto x2 = col_latitudes; - for (int ivar = 0; ivar < nvars; ++ivar) { - const auto var_org = linoz_params.data_orig[ivar]; - const auto y1 = ekat::subview(var_org,kk); - const auto y2 = ekat::subview(linoz_params.data_horiz[ivar],kk); - horiz_interp.lin_interp(team, x1, x2, y1, y2, kk); - } - }); - Kokkos::fence(); - - - // FIXME: Does ekat or kokkos have a call to transpose a view? - const auto policy_transpose = ESU::get_default_team_policy(ncol*linoz_data_nlev,1); - Kokkos::parallel_for("transpose_horization_data", policy_transpose, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int icol = team.league_rank() / linoz_data_nlev; - const int ilev = team.league_rank() % linoz_data_nlev; - for (int ivar = 0; ivar < nvars; ++ivar) { - const auto input = linoz_params.data_horiz[ivar]; - const auto output = linoz_data_out.data[ivar]; - output(icol, ilev) = input(ilev, icol); - }// ivar - }); - Kokkos::fence(); - - } // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp +// FIXME: I need to convert for loops to Kokkos loops. KOKKOS_INLINE_FUNCTION void vert_interp(int ncol, int levsiz, @@ -419,139 +209,12 @@ void vert_interp(int ncol, } // vert_interp -inline void perform_vertical_interpolation(const LinozReaderParams& linoz_params, - const const_view_2d& p_mid, - LinozData& non_interpolated_linoz, - LinozData& interpolated_linoz) -{ - const int ncol = interpolated_linoz.ncol_; - const int nlev = interpolated_linoz.nlev_; - - const int nvars = non_interpolated_linoz.nvars_; - const int nlevs_linoz = non_interpolated_linoz.nlev_; - - const auto kupper = linoz_params.kupper; - const auto levs = linoz_params.levs; - const auto pin = linoz_params.pin; - - - const auto policy_interp = ESU::get_default_team_policy(nvars, 1); - Kokkos::parallel_for("vertical_interpolation_linoz", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - const int ivar = team.league_rank(); - const auto var_non_inter = non_interpolated_linoz.data[ivar]; - const auto var_inter = interpolated_linoz.data[ivar]; - vert_interp(ncol, - nlevs_linoz, - nlev, - pin, - p_mid, - var_non_inter, - var_inter, - //work array - kupper); - }); - Kokkos::fence(); - -}//perform_vertical_interpolation - -// This function is based on update_spa_timestate -inline void update_linoz_timestate(const util::TimeStamp& ts, - LinozTimeState& time_state, - std::shared_ptr linoz_reader, - const LinozReaderParams& linoz_params, - LinozData& data_beg, - LinozData& data_end) -{ - // Now we check if we have to update the data that changes monthly - // NOTE: This means that SPA assumes monthly data to update. Not - // any other frequency. - const auto month = ts.get_month() - 1; // Make it 0-based - if (month != time_state.current_month) { - // Update the SPA time state information - time_state.current_month = month; - time_state.t_beg_month = util::TimeStamp({ts.get_year(),month+1,1}, {0,0,0}).frac_of_year_in_days(); - time_state.days_this_month = util::days_in_month(ts.get_year(),month+1); - - // // Copy spa_end'data into spa_beg'data, and read in the new spa_end - for (int ivar = 0; ivar < data_beg.nvars_; ++ivar) - { - Kokkos::deep_copy(data_beg.data[ivar],data_end.data[ivar]); - } - - // Update the SPA forcing data for this month and next month - // Start by copying next months data to this months data structure. - // NOTE: If the timestep is bigger than monthly this could cause the wrong values - // to be assigned. A timestep greater than a month is very unlikely so we - // will proceed. - int next_month = (time_state.current_month + 1) % 12; - linoz_reader->read_variables(next_month); - perform_horizontal_interpolation(linoz_params, data_end); - // - } // end if -} // update_linoz_timestate - KOKKOS_INLINE_FUNCTION Real linear_interp(const Real& x0, const Real& x1, const Real& t) { return (1 - t)*x0 + t*x1; } // linear_interp -KOKKOS_INLINE_FUNCTION -view_1d get_var_column (const LinozData& data, - const int icol, - const int ivar) -{ - return ekat::subview(data.data[ivar],icol); -} // get_var_column -// This function is based on the SPA::perform_time_interpolation function. - inline void perform_time_interpolation( - const LinozTimeState& time_state, - const LinozData& data_beg, - const LinozData& data_end, - const LinozData& data_out) -{ - // NOTE: we *assume* data_beg and data_end have the *same* hybrid v coords. - // IF this ever ceases to be the case, you can interp those too. - // Gather time stamp info - auto& t_now = time_state.t_now; - auto& t_beg = time_state.t_beg_month; - auto& delta_t = time_state.days_this_month; - - // We can ||ize over columns as well as over variables and bands - const int num_vars = data_beg.nvars_; - const int outer_iters = data_beg.ncol_*num_vars; - const int num_vert = data_beg.nlev_; - const auto policy = ESU::get_default_team_policy(outer_iters, num_vert); - - auto delta_t_fraction = (t_now-t_beg) / delta_t; - - EKAT_REQUIRE_MSG (delta_t_fraction>=0 && delta_t_fraction<=1, - "Error! Convex interpolation with coefficient out of [0,1].\n" - " t_now : " + std::to_string(t_now) + "\n" - " t_beg : " + std::to_string(t_beg) + "\n" - " delta_t: " + std::to_string(delta_t) + "\n"); - - Kokkos::parallel_for("linoz_time_interp_loop", policy, - KOKKOS_LAMBDA(const Team& team) { - - // The policy is over ncols*num_vars, so retrieve icol/ivar - const int icol = team.league_rank() / num_vars; - const int ivar = team.league_rank() % num_vars; - - // Get column of beg/end/out variable - auto var_beg = get_var_column (data_beg,icol,ivar); - auto var_end = get_var_column (data_end,icol,ivar); - auto var_out = get_var_column (data_out,icol,ivar); - - Kokkos::parallel_for (Kokkos::TeamVectorRange(team,num_vert), - [&] (const int& k) { - var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); - }); - }); - Kokkos::fence(); -} // perform_time_interpolation - // time[3]={year,month, day} inline util::TimeStamp convert_date(const int date) { @@ -624,15 +287,38 @@ create_horiz_remapper ( const std::string& trace_data_file, const std::string& map_file, const std::vector& var_names, - bool& has_ps + TracerFileType& tracer_file_type ) { using namespace ShortFieldTagsNames; scorpio::register_file(trace_data_file,scorpio::Read); - const int nlevs_data = scorpio::get_dimlen(trace_data_file,"lev"); + + // by default, I am assuming a zonal file. + tracer_file_type = ZONAL; + + int nlevs_data =-1; + if (scorpio::has_var(trace_data_file,"lev")){ + nlevs_data = scorpio::get_dimlen(trace_data_file,"lev"); + } + const bool has_altitude = scorpio::has_var(trace_data_file,"altitude"); + + // This type of files use altitude (zi) for vertical interpolation + if (has_altitude){ + nlevs_data = scorpio::get_dimlen(trace_data_file,"altitude"); + tracer_file_type= VERT_EMISSION; + } + + EKAT_REQUIRE_MSG (nlevs_data !=-1 , + "Error: The file does not contain either lev or altitude. \n"); + const int ncols_data = scorpio::get_dimlen(trace_data_file,"ncol"); - has_ps = scorpio::has_var(trace_data_file,"PS"); + + // This type of files use model pressure (pmid) for vertical interpolation + if (scorpio::has_var(trace_data_file,"PS")) { + tracer_file_type= FORMULA_PS; + } + scorpio::release_file(trace_data_file); // We could use model_grid directly if using same num levels, @@ -640,6 +326,11 @@ create_horiz_remapper ( auto horiz_interp_tgt_grid = model_grid->clone("tracer_horiz_interp_tgt_grid",true); horiz_interp_tgt_grid->reset_num_vertical_lev(nlevs_data); + if ( has_altitude ) { + horiz_interp_tgt_grid->reset_field_tag_name(LEV,"altitude"); + horiz_interp_tgt_grid->reset_field_tag_name(ILEV,"altitude_int"); + } + const int ncols_model = model_grid->get_num_global_dofs(); std::shared_ptr remapper; if (ncols_data==ncols_model) { @@ -659,17 +350,34 @@ create_horiz_remapper ( remapper->registration_begins(); const auto tgt_grid = remapper->get_tgt_grid(); const auto layout_2d = tgt_grid->get_2d_scalar_layout(); + const auto layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); + // FieldLayout layout_3d_mid; + // if ( has_altitude ) { + + // auto make_layout = [](const std::vector& extents, + // const std::vector& names) + // { + // std::vector tags(extents.size(),CMP); + // return FieldLayout(tags,extents,names); + // }; + // layout_3d_mid = make_layout({ncols_model, nlevs_data}, + // {"ncol","altitude"}); + // // FieldLayout({FieldTag::Column,CMP},{ncols_model,nlevs_data}); + // } else { + // layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); + // } + const auto nondim = ekat::units::Units::nondimensional(); - for(auto var_name : var_names){ + for (auto var_name : var_names){ Field ifield(FieldIdentifier(var_name, layout_3d_mid, nondim,tgt_grid->name())); ifield.allocate_view(); remapper->register_field_from_tgt (ifield); } // zonal files do not have the PS variable. - if (has_ps) + if (tracer_file_type == FORMULA_PS) { Field ps (FieldIdentifier("PS", layout_2d, nondim,tgt_grid->name())); ps.allocate_view(); @@ -711,13 +419,12 @@ update_tracer_data_from_file( tracer_horiz_interp.remap(/*forward = */ true); // const int nvars =tracer_data.nvars_; - // for (int i = 0; i < nvars; ++i) { tracer_data.data[i] = tracer_horiz_interp.get_tgt_field (i).get_view< Real**>(); } - if (tracer_data.has_ps) { + if (tracer_data.file_type == FORMULA_PS) { // Recall, the fields are registered in the order: tracers, ps // 3. Copy from the tgt field of the remapper into the spa_data tracer_data.ps = tracer_horiz_interp.get_tgt_field(nvars).get_view< Real*>(); @@ -729,7 +436,7 @@ update_tracer_timestate( std::shared_ptr& scorpio_reader, const util::TimeStamp& ts, AbstractRemapper& tracer_horiz_interp, - LinozTimeState& time_state, + TracerTimeState& time_state, TracerData& data_tracer_beg, TracerData& data_tracer_end) { @@ -769,7 +476,7 @@ update_tracer_timestate( // This function is based on the SPA::perform_time_interpolation function. inline void perform_time_interpolation( - const LinozTimeState& time_state, + const TracerTimeState& time_state, const TracerData& data_tracer_beg, const TracerData& data_tracer_end, const TracerData& data_tracer_out) @@ -786,6 +493,7 @@ update_tracer_timestate( const auto data_end = data_tracer_end.data; const auto data_out = data_tracer_out.data; + const auto file_type = data_tracer_out.file_type; const auto ps_beg = data_tracer_beg.ps; const auto ps_end = data_tracer_end.ps; @@ -824,8 +532,8 @@ update_tracer_timestate( [&] (const int& k) { var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); }); - - if(ivar==1){ + // linoz files do not have ps variables. + if(ivar==1 && file_type == FORMULA_PS){ ps_out(icol) = linear_interp(ps_beg(icol), ps_end(icol),delta_t_fraction); } @@ -840,10 +548,6 @@ compute_source_pressure_levels( const const_view_1d& hyam, const const_view_1d& hybm) { - using ExeSpace = typename KT::ExeSpace; - using ESU = ekat::ExeSpaceUtils; - using C = scream::physics::Constants; - constexpr auto P0 = C::P0; const int ncols = ps_src.extent(0); const int num_vert_packs = p_src.extent(1); @@ -860,6 +564,45 @@ compute_source_pressure_levels( } // compute_source_pressure_levels +// Linoz NetCDF files use levs instead of formula_terms. +// This function allocates a view, so we need to do it during initialization. +// Thus, we assume that source pressure is independent of time, +// which is the case for Linoz files (zonal file). + inline void compute_p_src_zonal_files(const std::string& tracer_file_name, + const view_2d& p_src) + { + EKAT_REQUIRE_MSG (p_src.data()!=0 , + "Error: p_src has not been allocated. \n"); + // Read in levs in start/end data + // FIXME: units are mbar; how can I get units using scorpio interface + auto nondim = ekat::units::Units::nondimensional(); + scorpio::register_file(tracer_file_name,scorpio::Read); + const int nlevs_data = scorpio::get_dimlen(tracer_file_name,"lev"); + view_1d_host levs_h("levs_h", nlevs_data); + scorpio::read_var(tracer_file_name, "lev", levs_h.data()); + scorpio::release_file(tracer_file_name); + view_1d levs("levs", nlevs_data); + Kokkos::deep_copy(levs, levs_h); + + const int ncol = p_src.extent(0); + EKAT_REQUIRE_MSG (p_src.extent(1) == nlevs_data , + "Error: p_src has a different number of levels than the source data. \n"); + + const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); + const int pi =haero::Constants::pi; + Kokkos::parallel_for("pressure_computation", policy_pressure, + KOKKOS_LAMBDA(const Team& team) { + const int icol = team.league_rank(); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), + [&] (const Int& kk) { + // mbar->pascals + // FIXME: Does EAMxx have a better method to convert units?" + p_src(icol, kk) = levs(kk)*100; + }); + }); + Kokkos::fence(); + } + inline void perform_vertical_interpolation( const view_2d& p_src_c, @@ -867,12 +610,9 @@ perform_vertical_interpolation( const TracerData& input, const view_2d output[]) { - using ExeSpace = typename KT::ExeSpace; - using ESU = ekat::ExeSpaceUtils; - using LIV = ekat::LinInterp; - // At this stage, begin/end must have the same horiz dimensions EKAT_REQUIRE(input.ncol_==output[0].extent(0)); + #if 1 // FIXME: I was encountering a compilation error when using const_view_2d. // The issue is fixed by https://github.com/E3SM-Project/EKAT/pull/346. @@ -931,16 +671,104 @@ perform_vertical_interpolation( Kokkos::fence(); } +// rebin is a port from: https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 +inline void rebin(int nsrc, int ntrg, + const const_view_1d& src_x, + const Real trg_x[], + const view_1d& src, + const view_1d& trg) { + + for (int i = 0; i < ntrg; ++i) { + Real tl = trg_x[i]; + if (tl < src_x(nsrc)) { + int sil = 0; + for (; sil <= nsrc; ++sil) { + if (tl <= src_x(sil)) { + break; + } + } + Real tu = trg_x[i + 1]; + int siu = 0; + for (; siu <= nsrc; ++siu) { + if (tu <= src_x(siu)) { + break; + } + } + Real y = 0.0; + sil = haero::max(sil, 1); + siu = haero::min(siu, nsrc); + for (int si = sil; si <= siu; ++si) { + int si1 = si - 1; + Real sl = haero::max(tl, src_x(si1)); + Real su = haero::min(tu, src_x(si)); + y += (su - sl) * src(si1); + } + trg(i) = y / (trg_x[i + 1] - trg_x[i]); + } else { + trg(i) = 0.0; + } + } +}// rebin + +inline void perform_vertical_interpolation(const const_view_1d& altitude_int, + const const_view_2d& zi, + const TracerData& input, + const view_2d output[]) +{ + EKAT_REQUIRE_MSG (input.file_type == VERT_EMISSION, + "Error! vertical interpolation only with altitude variable. \n"); + const int ncols = input.ncol_; + const int num_vars = input.nvars_; + const int ntrg = output[0].extent(1); + const int num_vert_packs = ntrg; + const int outer_iters = ncols*num_vars; + const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); + // FIXME: Get m2km from emaxx. + const Real m2km =1e-3; + const auto& src_x = altitude_int; + const int nsrc = input.nlev_; + constexpr int pver = mam4::nlev; + const int pverp = pver + 1; + + Kokkos::parallel_for("tracer_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(const Team& team) { + + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + const auto src = ekat::subview(input.data[ivar],icol); + const auto trg = ekat::subview(output[ivar],icol); + + // trg_x + Real trg_x[pver+1]; + // I am trying to do this: + // model_z(1:pverp) = m2km * state(c)%zi(i,pverp:1:-1) + for (int i = 0; i < pverp; ++i) + { + trg_x[pverp-i-1] = m2km * zi(icol,i); + } + team.team_barrier(); + + rebin(nsrc, ntrg, + src_x, + trg_x, + src, + trg); + }); +} + inline void advance_tracer_data(std::shared_ptr& scorpio_reader, AbstractRemapper& tracer_horiz_interp, const util::TimeStamp& ts, - LinozTimeState& time_state, + TracerTimeState& time_state, TracerData& data_tracer_beg, TracerData& data_tracer_end, TracerData& data_tracer_out, const view_2d& p_src, const const_view_2d& p_tgt, + const const_view_1d& zi_src, + const const_view_2d& zi_tgt, const view_2d output[]) { @@ -960,24 +788,34 @@ advance_tracer_data(std::shared_ptr& scorpio_reader, data_tracer_beg, data_tracer_end, data_tracer_out); - if (data_tracer_out.has_ps){ + + if (data_tracer_out.file_type == FORMULA_PS ) + { // Step 2. Compute source pressure levels compute_source_pressure_levels( data_tracer_out.ps, p_src, data_tracer_out.hyam, data_tracer_out.hybm); - } - // Step 3. Perform vertical interpolation - + if (data_tracer_out.file_type == FORMULA_PS || data_tracer_out.file_type == ZONAL) + { perform_vertical_interpolation( - p_src, - p_tgt, - data_tracer_out, - output); + p_src, + p_tgt, + data_tracer_out, + output); + } else if (data_tracer_out.file_type == VERT_EMISSION) { + perform_vertical_interpolation( + zi_src, + zi_tgt, + data_tracer_out, + output); + + } + }// advance_tracer_data diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 083a7eb51b1..13354686829 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -11,11 +11,14 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] mam4_aero_microphys: - mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc + mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc + mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc mam4_chlorine_loading_ymd : 20100101 mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/RSF_GT200nm_v3.0_c080811.nc mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/temp_prs_GT200nm_JPL10_c130206.nc + mam4_bc_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc + mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc grids_manager: Type: Mesh Free geo_data_source: IC_FILE From 7768d7dc41d1ca36838bba44c12ada12d0ae79fa Mon Sep 17 00:00:00 2001 From: Mingxuan Wu Date: Thu, 18 Jul 2024 12:33:07 -0700 Subject: [PATCH 14/31] add code to compute orbital parameters, and change CMake file to use Fortran code from rrtmgp --- .../eamxx/src/physics/mam/CMakeLists.txt | 3 +- ...mxx_mam_microphysics_process_interface.cpp | 59 ++++++++++++++----- ...mxx_mam_microphysics_process_interface.hpp | 12 ++++ .../physics/mam/impl/gas_phase_chemistry.cpp | 17 ++---- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/components/eamxx/src/physics/mam/CMakeLists.txt b/components/eamxx/src/physics/mam/CMakeLists.txt index 9874f79f9eb..fb6a48733f8 100644 --- a/components/eamxx/src/physics/mam/CMakeLists.txt +++ b/components/eamxx/src/physics/mam/CMakeLists.txt @@ -42,6 +42,7 @@ add_subdirectory(${EXTERNALS_SOURCE_DIR}/mam4xx ${CMAKE_BINARY_DIR}/externals/ma # EAMxx mam4xx-based atmospheric processes add_library(mam eamxx_mam_microphysics_process_interface.cpp + ${SCREAM_BASE_DIR}/src/physics/rrtmgp/shr_orb_mod_c2f.F90 eamxx_mam_optics_process_interface.cpp eamxx_mam_dry_deposition_process_interface.cpp eamxx_mam_aci_process_interface.cpp @@ -54,7 +55,7 @@ target_include_directories(mam PUBLIC ${EXTERNALS_SOURCE_DIR}/haero ${EXTERNALS_SOURCE_DIR}/mam4xx/src ) -target_link_libraries(mam PUBLIC physics_share scream_share mam4xx haero) +target_link_libraries(mam PUBLIC physics_share csm_share scream_share mam4xx haero) #if (NOT SCREAM_LIB_ONLY) # add_subdirectory(tests) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 955a07f311e..51e0021690a 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "share/util/scream_common_physics_functions.hpp" #include "scream_config.h" // for SCREAM_CIME_BUILD @@ -12,6 +13,10 @@ #include "impl/compute_o3_column_density.cpp" #include "impl/compute_water_content.cpp" #include "impl/gas_phase_chemistry.cpp" +#include "physics/rrtmgp/shr_orb_mod_c2f.hpp" + +// For EKAT units package +#include "ekat/util/ekat_units.hpp" @@ -276,6 +281,16 @@ void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { void MAMMicrophysics::initialize_impl(const RunType run_type) { step_ = 0; + + // Determine orbital year. If orbital_year is negative, use current year + // from timestamp for orbital year; if positive, use provided orbital year + // for duration of simulation. + m_orbital_year = m_params.get("orbital_year",-9999); + + // Get orbital parameters from yaml file + m_orbital_eccen = m_params.get("orbital_eccentricity",-9999); + m_orbital_obliq = m_params.get("orbital_obliquity" ,-9999); + m_orbital_mvelp = m_params.get("orbital_mvelp" ,-9999); // populate the wet and dry atmosphere states with views from fields and // the buffer @@ -623,6 +638,33 @@ void MAMMicrophysics::run_impl(const double dt) { const auto& lwc =lwc_; + + // Compute orbital parameters; these are used both for computing + // the solar zenith angle. + auto ts = timestamp(); + double obliqr, lambm0, mvelpp; + auto orbital_year = m_orbital_year; + auto eccen = m_orbital_eccen; + auto obliq = m_orbital_obliq; + auto mvelp = m_orbital_mvelp; + if (eccen >= 0 && obliq >= 0 && mvelp >= 0) { + // use fixed orbital parameters; to force this, we need to set + // orbital_year to SHR_ORB_UNDEF_INT, which is exposed through + // our c2f bridge as shr_orb_undef_int_c2f + orbital_year = shr_orb_undef_int_c2f; + } else if (orbital_year < 0) { + // compute orbital parameters based on current year + orbital_year = ts.get_year(); + } + shr_orb_params_c2f(&orbital_year, &eccen, &obliq, &mvelp, + &obliqr, &lambm0, &mvelpp); + // Use the orbital parameters to calculate the solar declination and eccentricity factor + double delta, eccf; + auto calday = ts.frac_of_year_in_days() + 1; // Want day + fraction; calday 1 == Jan 1 0Z + shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, + obliqr, &delta, &eccf); + + // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const ThreadTeam& team) { const int icol = team.league_rank(); // column index @@ -652,7 +694,7 @@ void MAMMicrophysics::run_impl(const double dt) { // calculate o3 column densities (first component of col_dens in Fortran code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); - // impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); + impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); // set up photolysis work arrays for this column. mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; @@ -671,16 +713,9 @@ void MAMMicrophysics::run_impl(const double dt) { Real esfact = 0.0; // FIXME: earth-sun distance factor const auto& photo_rates_icol = ekat::subview(photo_rates, icol); -#if 0 mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, atm.hydrostatic_dp, atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, atm.liquid_mixing_ratio, atm.cloud_fraction, esfact, photo_table, photo_work_arrays_icol); -#endif - // compute external forcings at time t(n+1) [molecules/cm^3/s] - constexpr int extcnt = mam4::gas_chemistry::extcnt; - view_2d extfrc; // FIXME: where to allocate? (nlev, extcnt) - mam4::mo_setext::Forcing forcings[extcnt]; // FIXME: forcings seem to require file data - //mam4::mo_setext::extfrc_set(forcings, extfrc); // compute aerosol microphysics on each vertical level within this column Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { @@ -735,16 +770,12 @@ void MAMMicrophysics::run_impl(const double dt) { for (int i = 0; i < mam4::mo_photo::phtcnt; ++i) { photo_rates_k[i] = photo_rates_icol(k, i); } - /*Real extfrc_k[extcnt]; - for (int i = 0; i < extcnt; ++i) { - extfrc_k[i] = extfrc(k, i); - }*/ constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" // NOTE: we compute invariants here and pass them out to use later with // NOTE: setsox Real invariants[nfs]; - /*impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k, extfrc_k, vmr, invariants);*/ + impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, + photo_rates_k, vmr, invariants); //---------------------- // Aerosol microphysics diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 074d9b3fd36..89d76e3a1d7 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -93,6 +93,18 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // number of horizontal columns and vertical levels int ncol_, nlev_; + // The orbital year, used for zenith angle calculations: + // If > 0, use constant orbital year for duration of simulation + // If < 0, use year from timestamp for orbital parameters + Int m_orbital_year; + + // Orbital parameters, used for zenith angle calculations. + // If >= 0, bypass computation based on orbital year and use fixed parameters + // If < 0, compute based on orbital year, specified above + Real m_orbital_eccen; // Eccentricity + Real m_orbital_obliq; // Obliquity + Real m_orbital_mvelp; // Vernal Equinox Mean Longitude of Perihelion + // configuration data (for the moment, we plan to be able to move this to // the device, so we can't use C++ strings) struct Config { diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index 592da804464..dbba6393429 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -371,7 +371,6 @@ mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, KOKKOS_INLINE_FUNCTION void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real pdel, Real dt, const Real photo_rates[mam4::mo_photo::phtcnt], // in - const Real extfrc[mam4::gas_chemistry::extcnt], // in Real q[mam4::gas_chemistry::gas_pcnst], // VMRs, inout Real invariants[mam4::gas_chemistry::nfs]) { // out // constexpr Real rga = 1.0/haero::Constants::gravity; @@ -380,13 +379,13 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // The following things are chemical mechanism dependent! See mam4xx/src/mam4xx/gas_chem_mechanism.hpp) constexpr int gas_pcnst = mam4::gas_chemistry::gas_pcnst; // number of gas phase species constexpr int rxntot = mam4::gas_chemistry::rxntot; // number of chemical reactions - constexpr int extcnt = mam4::gas_chemistry::extcnt; // number of species with external forcing - constexpr int indexm = 0; // FIXME: index of total atm density in invariants array + constexpr int indexm = mam4::gas_chemistry::indexm; // index of total atm density in invariant array constexpr int phtcnt = mam4::mo_photo::phtcnt; // number of photolysis reactions constexpr int itermax = mam4::gas_chemistry::itermax; constexpr int clscnt4 = mam4::gas_chemistry::clscnt4; + constexpr int nfs = mam4::gas_chemistry::nfs; // NOTE: vvv these arrays were copied from mam4xx/gas_chem_mechanism.hpp vvv constexpr int permute_4[gas_pcnst] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, @@ -420,6 +419,9 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // ... compute the column's invariants // Real h2ovmr = q[0]; // setinv(invariants, temp, h2ovmr, q, pmid); FIXME: not ported yet + for (int i = 0; i < nfs; ++i) { + invariants[i] = 0.1; + } // ... set rates for "tabular" and user specified reactions Real reaction_rates[rxntot]; @@ -439,13 +441,6 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // Photolysis rates at time = t(n+1) //=================================== - // compute the rate of change from forcing - Real extfrc_rates[extcnt]; // [1/cm^3/s] - for (int mm = 0; mm < extcnt; ++mm) { - if (mm != synoz_ndx) { - extfrc_rates[mm] = extfrc[mm] / invariants[indexm]; - } - } // ... Form the washout rates Real het_rates[gas_pcnst]; @@ -478,7 +473,7 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // solve chemical system implicitly Real prod_out[clscnt4], loss_out[clscnt4]; - mam4::gas_chemistry::imp_sol(q, reaction_rates, het_rates, extfrc_rates, dt, + mam4::gas_chemistry::imp_sol(q, reaction_rates, het_rates, dt, permute_4, clsmap_4, factor, epsilon, prod_out, loss_out); // save h2so4 change by gas phase chem (for later new particle nucleation) From c13eccc6044e0fc18bac951a4c60ec016f5b1d5e Mon Sep 17 00:00:00 2001 From: Mingxuan Wu Date: Mon, 22 Jul 2024 06:09:50 -0700 Subject: [PATCH 15/31] add code to calculate zeith_angle, surf_albedo, and eccf remove code for lwc, only keep one hookup method uncomment functions and fix temp input for amiphys_intr invoke setinv and only call it once in icol loop Using mam4::utils::extract_stateq_from_prognostics in compute_water_content. Use functions from mam4::utils to transfer data from prog to temp arrays. Fixing order of forcing files. --- .../cime_config/namelist_defaults_scream.xml | 13 + .../mam4xx/aero_microphysics/shell_commands | 17 + ...mxx_mam_microphysics_process_interface.cpp | 1259 ++++++++++------- ...mxx_mam_microphysics_process_interface.hpp | 13 +- .../eamxx/src/physics/mam/impl/README.md | 2 +- .../mam/impl/compute_o3_column_density.cpp | 73 +- .../mam/impl/compute_water_content.cpp | 90 +- .../physics/mam/impl/gas_phase_chemistry.cpp | 357 +++-- .../src/physics/mam/impl/helper_micro.hpp | 1160 +++++++-------- .../src/physics/mam/impl/mam4_amicphys.cpp | 1192 ++++++++-------- .../eamxx/src/physics/mam/mam_coupling.hpp | 23 +- 11 files changed, 2193 insertions(+), 2006 deletions(-) create mode 100644 components/eamxx/cime_config/testdefs/testmods_dirs/scream/mam4xx/aero_microphysics/shell_commands diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 9a7975f01ba..9d9d83201c3 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -268,6 +268,19 @@ be lost if SCREAM_HACK_XML is not enabled. + + + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + 20100101 + type="file" doc=""> ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/RSF_GT200nm_v3.0_c080811.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/temp_prs_GT200nm_JPL10_c130206.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/mam4_mode1_rrtmg_aeronetdust_c20240206.nc diff --git a/components/eamxx/cime_config/testdefs/testmods_dirs/scream/mam4xx/aero_microphysics/shell_commands b/components/eamxx/cime_config/testdefs/testmods_dirs/scream/mam4xx/aero_microphysics/shell_commands new file mode 100644 index 00000000000..1d6757a5bd9 --- /dev/null +++ b/components/eamxx/cime_config/testdefs/testmods_dirs/scream/mam4xx/aero_microphysics/shell_commands @@ -0,0 +1,17 @@ + +#!/bin/sh +#------------------------------------------------------ +# MAM4xx adds additionaltracers to the simulation +# Increase number of tracers for MAM4xx simulations +#------------------------------------------------------ + +$CIMEROOT/../components/eamxx/cime_config/testdefs/testmods_dirs/scream/mam4xx/update_eamxx_num_tracers.sh -b + +#------------------------------------------------------ +#Update IC file and add drydep process +#------------------------------------------------------ +$CIMEROOT/../components/eamxx/scripts/atmchange initial_conditions::Filename='$DIN_LOC_ROOT/atm/scream/init/screami_mam4xx_ne4np4L72_c20240208.nc' -b +$CIMEROOT/../components/eamxx/scripts/atmchange physics::atm_procs_list="mac_aero_mic,rrtmgp,mam4_aero_microphys" -b + + + diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 51e0021690a..2b7f29da991 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -1,13 +1,14 @@ +#include // for serial NetCDF file reads on MPI root + +#include +#include #include #include #include #include -#include "share/util/scream_common_physics_functions.hpp" - -#include "scream_config.h" // for SCREAM_CIME_BUILD -#include // for serial NetCDF file reads on MPI root -#include +#include "scream_config.h" // for SCREAM_CIME_BUILD +#include "share/util/scream_common_physics_functions.hpp" // NOTE: see the impl/ directory for the contents of the impl namespace #include "impl/compute_o3_column_density.cpp" @@ -18,16 +19,15 @@ // For EKAT units package #include "ekat/util/ekat_units.hpp" +// For EKAT subview +#include +#include +namespace scream { -namespace scream -{ - -MAMMicrophysics::MAMMicrophysics( - const ekat::Comm& comm, - const ekat::ParameterList& params) - : AtmosphereProcess(comm, params), - aero_config_() { +MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, + const ekat::ParameterList ¶ms) + : AtmosphereProcess(comm, params), aero_config_() { configure(params); } @@ -35,42 +35,43 @@ AtmosphereProcessType MAMMicrophysics::type() const { return AtmosphereProcessType::Physics; } -std::string MAMMicrophysics::name() const { - return "mam4_micro"; -} +std::string MAMMicrophysics::name() const { return "mam4_micro"; } namespace { -void set_data_file(const char *name, const char *path, char location[MAX_FILENAME_LEN]) { +void set_data_file(const char *name, const char *path, + char location[MAX_FILENAME_LEN]) { EKAT_REQUIRE_MSG(strlen(SCREAM_DATA_DIR) + strlen(path) < MAX_FILENAME_LEN, - "Error! " << name << " path is too long (must be < " << MAX_FILENAME_LEN << " characters)"); + "Error! " << name << " path is too long (must be < " + << MAX_FILENAME_LEN << " characters)"); sprintf(location, "%s/%s", SCREAM_DATA_DIR, path); } -} +} // namespace -#define set_file_location(data_file, path) set_data_file(#data_file, path, data_file) +#define set_file_location(data_file, path) \ + set_data_file(#data_file, path, data_file) void MAMMicrophysics::set_defaults_() { - config_.amicphys.do_cond = true; + config_.amicphys.do_cond = true; config_.amicphys.do_rename = true; config_.amicphys.do_newnuc = true; - config_.amicphys.do_coag = true; + config_.amicphys.do_coag = true; - config_.amicphys.nucleation = {}; - config_.amicphys.nucleation.dens_so4a_host = 1770.0; - config_.amicphys.nucleation.mw_so4a_host = 115.0; - config_.amicphys.nucleation.newnuc_method_user_choice = 2; + config_.amicphys.nucleation = {}; + config_.amicphys.nucleation.dens_so4a_host = 1770.0; + config_.amicphys.nucleation.mw_so4a_host = 115.0; + config_.amicphys.nucleation.newnuc_method_user_choice = 2; config_.amicphys.nucleation.pbl_nuc_wang2008_user_choice = 1; - config_.amicphys.nucleation.adjust_factor_pbl_ratenucl = 1.0; - config_.amicphys.nucleation.accom_coef_h2so4 = 1.0; + config_.amicphys.nucleation.adjust_factor_pbl_ratenucl = 1.0; + config_.amicphys.nucleation.accom_coef_h2so4 = 1.0; config_.amicphys.nucleation.newnuc_adjust_factor_dnaitdt = 1.0; // these parameters guide the coupling between parameterizations // NOTE: mam4xx was ported with these parameters fixed, so it's probably not // NOTE: safe to change these without code modifications. config_.amicphys.gaexch_h2so4_uptake_optaa = 2; - config_.amicphys.newnuc_h2so4_conc_optaa = 2; + config_.amicphys.newnuc_h2so4_conc_optaa = 2; //=========================================================== // default data file locations (relative to SCREAM_DATA_DIR) @@ -80,235 +81,326 @@ void MAMMicrophysics::set_defaults_() { // e3smv2/bld/namelist_files/namelist_defaults_eam.xml // photolysis - // set_file_location(config_.photolysis.rsf_file, "../waccm/phot/RSF_GT200nm_v3.0_c080811.nc"); - // set_file_location(config_.photolysis.xs_long_file, "../waccm/phot/temp_prs_GT200nm_JPL10_c130206.nc"); + // set_file_location(config_.photolysis.rsf_file, + // "../waccm/phot/RSF_GT200nm_v3.0_c080811.nc"); + // set_file_location(config_.photolysis.xs_long_file, + // "../waccm/phot/temp_prs_GT200nm_JPL10_c130206.nc"); // stratospheric chemistry - // set_file_location(config_.linoz.chlorine_loading_file, "../cam/chem/trop_mozart/ub/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"); + // set_file_location(config_.linoz.chlorine_loading_file, + // "../cam/chem/trop_mozart/ub/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"); // dry deposition - set_file_location(config_.drydep.srf_file, "../cam/chem/trop_mam/atmsrf_ne4pg2_200527.nc"); + set_file_location(config_.drydep.srf_file, + "../cam/chem/trop_mam/atmsrf_ne4pg2_200527.nc"); } -void MAMMicrophysics::configure(const ekat::ParameterList& params) { +void MAMMicrophysics::configure(const ekat::ParameterList ¶ms) { set_defaults_(); // FIXME: implement "namelist" parsing } -void MAMMicrophysics::set_grids(const std::shared_ptr grids_manager) { +void MAMMicrophysics::set_grids( + const std::shared_ptr grids_manager) { using namespace ekat::units; Units nondim = Units::nondimensional(); - Units n_unit (1/kg,"#/kg"); // number mixing ratios [# / kg air] - const auto m2 = pow(m,2); - const auto s2 = pow(s,2); + Units n_unit(1 / kg, "#/kg"); // number mixing ratios [# / kg air] + const auto m2 = pow(m, 2); + const auto s2 = pow(s, 2); - grid_ = grids_manager->get_grid("Physics"); - const auto& grid_name = grid_->name(); + grid_ = grids_manager->get_grid("Physics"); + const auto &grid_name = grid_->name(); - ncol_ = grid_->get_num_local_dofs(); // number of columns on this rank - nlev_ = grid_->get_num_vertical_levels(); // number of levels per column + ncol_ = grid_->get_num_local_dofs(); // number of columns on this rank + nlev_ = grid_->get_num_vertical_levels(); // number of levels per column // get column geometry and locations - col_areas_ = grid_->get_geometry_data("area").get_view(); - col_latitudes_ = grid_->get_geometry_data("lat").get_view(); - col_longitudes_ = grid_->get_geometry_data("lon").get_view(); + col_areas_ = grid_->get_geometry_data("area").get_view(); + col_latitudes_ = grid_->get_geometry_data("lat").get_view(); + col_longitudes_ = grid_->get_geometry_data("lon").get_view(); // define the different field layouts that will be used for this process using namespace ShortFieldTagsNames; // layout for 2D (1d horiz X 1d vertical) variable - FieldLayout scalar2d_layout_col{ {COL}, {ncol_} }; + FieldLayout scalar2d_layout_col{{COL}, {ncol_}}; // layout for 3D (2d horiz X 1d vertical) variables - FieldLayout scalar3d_layout_mid{ {COL, LEV}, {ncol_, nlev_} }; + FieldLayout scalar3d_layout_mid{{COL, LEV}, {ncol_, nlev_}}; // At interfaces FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; // define fields needed in mam4xx // atmospheric quantities - add_field("omega", scalar3d_layout_mid, Pa/s, grid_name); // vertical pressure velocity - add_field("T_mid", scalar3d_layout_mid, K, grid_name); // Temperature - add_field("p_mid", scalar3d_layout_mid, Pa, grid_name); // total pressure + add_field("omega", scalar3d_layout_mid, Pa / s, + grid_name); // vertical pressure velocity + add_field("T_mid", scalar3d_layout_mid, K, + grid_name); // Temperature + add_field("p_mid", scalar3d_layout_mid, Pa, + grid_name); // total pressure // Total pressure [Pa] at interfaces add_field("p_int", scalar3d_layout_int, Pa, grid_name); - add_field("qv", scalar3d_layout_mid, kg/kg, grid_name, "tracers"); // specific humidity - add_field("qi", scalar3d_layout_mid, kg/kg, grid_name, "tracers"); // ice wet mixing ratio - add_field("ni", scalar3d_layout_mid, n_unit, grid_name, "tracers"); // ice number mixing ratio - add_field("pbl_height", scalar2d_layout_col, m, grid_name); // planetary boundary layer height - add_field("pseudo_density", scalar3d_layout_mid, Pa, grid_name); // p_del, hydrostatic pressure - add_field("phis", scalar2d_layout_col, m2/s2, grid_name); - add_field("cldfrac_tot", scalar3d_layout_mid, nondim, grid_name); // cloud fraction + add_field("qv", scalar3d_layout_mid, kg / kg, grid_name, + "tracers"); // specific humidity + add_field("qi", scalar3d_layout_mid, kg / kg, grid_name, + "tracers"); // ice wet mixing ratio + add_field("ni", scalar3d_layout_mid, n_unit, grid_name, + "tracers"); // ice number mixing ratio + add_field("pbl_height", scalar2d_layout_col, m, + grid_name); // planetary boundary layer height + add_field("pseudo_density", scalar3d_layout_mid, Pa, + grid_name); // p_del, hydrostatic pressure + add_field("phis", scalar2d_layout_col, m2 / s2, grid_name); + add_field("cldfrac_tot", scalar3d_layout_mid, nondim, + grid_name); // cloud fraction + add_field("sfc_alb_dir_vis", scalar2d_layout_col, nondim, + grid_name); // surface albedo shortwave, direct // droplet activation can alter cloud liquid and number mixing ratios - add_tracer("qc", grid_, kg/kg); // cloud liquid wet mixing ratio - add_tracer("nc", grid_, n_unit); // cloud liquid wet number mixing ratio + add_field("qc", scalar3d_layout_mid, kg / kg, grid_name, + "tracers"); // cloud liquid wet mixing ratio + add_field("nc", scalar3d_layout_mid, n_unit, grid_name, + "tracers"); // cloud liquid wet number mixing ratio // interstitial and cloudborne aerosol tracers of interest: mass (q) and // number (n) mixing ratios - for (int m = 0; m < mam_coupling::num_aero_modes(); ++m) { + for(int m = 0; m < mam_coupling::num_aero_modes(); ++m) { // interstitial aerosol tracers of interest: number (n) mixing ratios - const char* int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); - add_field(int_nmr_field_name, scalar3d_layout_mid, n_unit, grid_name, "tracers"); + const char *int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); + add_field(int_nmr_field_name, scalar3d_layout_mid, n_unit, + grid_name, "tracers"); // cloudborne aerosol tracers of interest: number (n) mixing ratios // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are // NOT advected - const char* cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); - add_field(cld_nmr_field_name, scalar3d_layout_mid, n_unit, grid_name); + const char *cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); + add_field(cld_nmr_field_name, scalar3d_layout_mid, n_unit, + grid_name); - for (int a = 0; a < mam_coupling::num_aero_species(); ++a) { + for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { // (interstitial) aerosol tracers of interest: mass (q) mixing ratios - const char* int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); - if (strlen(int_mmr_field_name) > 0) { - add_tracer(int_mmr_field_name, grid_, kg/kg); + const char *int_mmr_field_name = + mam_coupling::int_aero_mmr_field_name(m, a); + if(strlen(int_mmr_field_name) > 0) { + add_field(int_mmr_field_name, scalar3d_layout_mid, kg / kg, + grid_name, "tracers"); } // (cloudborne) aerosol tracers of interest: mass (q) mixing ratios // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are // NOT advected - const char* cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(m, a); - if (strlen(cld_mmr_field_name) > 0) { - add_field(cld_mmr_field_name, scalar3d_layout_mid, kg/kg, grid_name); + const char *cld_mmr_field_name = + mam_coupling::cld_aero_mmr_field_name(m, a); + if(strlen(cld_mmr_field_name) > 0) { + add_field(cld_mmr_field_name, scalar3d_layout_mid, kg / kg, + grid_name); } - } // end for loop for num species - } // end for loop for num modes + } // end for loop for num species + } // end for loop for num modes // aerosol-related gases: mass mixing ratios - for (int g = 0; g < mam_coupling::num_aero_gases(); ++g) { - const char* gas_mmr_field_name = mam_coupling::gas_mmr_field_name(g); - add_tracer(gas_mmr_field_name, grid_, kg/kg); + for(int g = 0; g < mam_coupling::num_aero_gases(); ++g) { + const char *gas_mmr_field_name = mam_coupling::gas_mmr_field_name(g); + add_field(gas_mmr_field_name, scalar3d_layout_mid, kg / kg, + grid_name, "tracers"); } // Tracers group -- do we need this in addition to the tracers above? In any // case, this call should be idempotent, so it can't hurt. add_group("tracers", grid_name, 1, Bundling::Required); - // Creating a Linoz reader and setting Linoz parameters involves reading data from a file - // and configuring the necessary parameters for the Linoz model. + // Creating a Linoz reader and setting Linoz parameters involves reading data + // from a file and configuring the necessary parameters for the Linoz model. { - // std::string linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; - linoz_file_name_ = - m_params.get("mam4_linoz_file_name"); - std::string spa_map_file=""; - std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col","cariolle_pscs"}; + // std::string + // linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; + linoz_file_name_ = m_params.get("mam4_linoz_file_name"); + std::string spa_map_file = ""; + std::vector var_names{"o3_clim", "o3col_clim", "t_clim", + "PmL_clim", "dPmL_dO3", "dPmL_dT", + "dPmL_dO3col", "cariolle_pscs"}; TracerFileType tracer_file_type; - LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,linoz_file_name_,spa_map_file, var_names, tracer_file_type); - LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader(LinozHorizInterp_,linoz_file_name_); + LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( + grid_, linoz_file_name_, spa_map_file, var_names, tracer_file_type); + LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader( + LinozHorizInterp_, linoz_file_name_); linoz_data_out_.set_file_type(tracer_file_type); - if (tracer_file_type == TracerFileType::FORMULA_PS) { - linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_,linoz_file_name_); - } + if(tracer_file_type == TracerFileType::FORMULA_PS) { + linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_, linoz_file_name_); + } linoz_data_beg_.set_file_type(tracer_file_type); linoz_data_end_.set_file_type(tracer_file_type); - } { - oxid_file_name_ = - m_params.get("mam4_oxid_file_name"); - std::string spa_map_file=""; - std::vector var_names{"O3","HO2","NO3","OH"}; - TracerFileType tracer_file_type; - TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper(grid_,oxid_file_name_, - spa_map_file, var_names, tracer_file_type); - TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader(TracerHorizInterp_,oxid_file_name_); - tracer_data_out_.set_file_type(tracer_file_type); - if (tracer_file_type == TracerFileType::FORMULA_PS) { - tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_,oxid_file_name_); - } - tracer_data_beg_.set_file_type(tracer_file_type); - tracer_data_end_.set_file_type(tracer_file_type); + oxid_file_name_ = m_params.get("mam4_oxid_file_name"); + std::string spa_map_file = ""; + std::vector var_names{"O3", "HO2", "NO3", "OH"}; + TracerFileType tracer_file_type; + TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( + grid_, oxid_file_name_, spa_map_file, var_names, tracer_file_type); + TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader( + TracerHorizInterp_, oxid_file_name_); + tracer_data_out_.set_file_type(tracer_file_type); + if(tracer_file_type == TracerFileType::FORMULA_PS) { + tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_, oxid_file_name_); + } + tracer_data_beg_.set_file_type(tracer_file_type); + tracer_data_end_.set_file_type(tracer_file_type); } { - vert_emis_file_name_= "cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc"; - std::string spa_map_file=""; - std::vector var_names{"BB"}; - - for (auto file_name :vert_emis_file_name_) - { - TracerFileType tracer_file_type; - auto hor_rem = scream::mam_coupling::create_horiz_remapper(grid_,file_name, - spa_map_file, var_names, tracer_file_type); - auto file_reader = scream::mam_coupling::create_tracer_data_reader(hor_rem, - file_name); - scream::mam_coupling::TracerData data_out, data_beg, data_end; - data_out.set_file_type(tracer_file_type); - data_beg.set_file_type(tracer_file_type); - data_end.set_file_type(tracer_file_type); - - VertEmissionsHorizInterp_.push_back(hor_rem); - VertEmissionsDataReader_.push_back(file_reader); - vert_emis_data_out_.push_back(data_out); - vert_emis_data_beg_.push_back(data_beg); - vert_emis_data_end_.push_back(data_end); - } + // FIXME: I will need to add this file per forcing file. + std::string spa_map_file = ""; + // NOTE: order of forcing species is important. + // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 ','pom_a4 ','bc_a4 ', + // 'num_a1 ','num_a2 ','num_a4 ','SOAG ' } + + std::string mam4_so2_verti_emiss_file_name = + //"cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc" + m_params.get("mam4_so2_verti_emiss_file_name"); + vert_emis_var_names_["so2"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; + vert_emis_file_name_["so2"] = mam4_so2_verti_emiss_file_name; + + // so4_a1 + std::string mam4_so4_a1_verti_emiss_file_name=base_path+"cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["so4_a1"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; + vert_emis_file_name_["so4_a1"] = mam4_so4_a1_verti_emiss_file_name; + + // so4_a2 + std::string mam4_so4_a2_verti_emiss_file_name=base_path+"cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["so4_a2"] = { "contvolc"}; + vert_emis_file_name_["so4_a2"] = mam4_so4_a2_verti_emiss_file_name; + + // pom_a4 + std::string mam4_pom_a4_verti_emiss_file_name=base_path+"cmip6_mam4_pom_a4_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["pom_a4"] = {"BB"}; + vert_emis_file_name_["pom_a4"] = mam4_pom_a4_verti_emiss_file_name; + + // cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc + std::string mam4_bc_a4_verti_emiss_file_name = + m_params.get("mam4_bc_a4_verti_emiss_file_name"); + vert_emis_file_name_["bc_a4"] = mam4_bc_a4_verti_emiss_file_name; + vert_emis_var_names_["bc_a4"] = {"BB"}; + + // num_a1 + std::string mam4_num_a1_verti_emiss_file_name=base_path+"cmip6_mam4_num_a1_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["num_a1"] = {"num_a1_SO4_ELEV_BB","num_a1_SO4_ELEV_ENE", "num_a1_SO4_ELEV_IND", "num_a1_SO4_ELEV_contvolc"}; + vert_emis_file_name_["num_a1"] = mam4_num_a1_verti_emiss_file_name; + + // num_a2 + std::string mam4_num_a2_verti_emiss_file_name=base_path+"cmip6_mam4_num_a2_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["num_a2"] = {"num_a2_SO4_ELEV_contvolc"}; + vert_emis_file_name_["num_a2"] = mam4_num_a2_verti_emiss_file_name; + + // num_a4 + // FIXME: why the sectors in this files are num_a1; I guess this should be num_a4? Is this a bug in the orginal nc files? + // QUESTION... + std::string mam4_num_a4_verti_emiss_file_name=base_path+"cmip6_mam4_num_a4_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["num_a4"] = {"num_a1_BC_ELEV_BB", "num_a1_POM_ELEV_BB"}; + vert_emis_file_name_["num_a4"] = mam4_num_a4_verti_emiss_file_name; + + //SOAG + // m_params.get("mam4_soag_verti_emiss_file_name"); + // FIXME: get this file from namelist + std::string base_path="/ascldap/users/odiazib/Documents/Oscar/CODE/eagles-project/scream_micro/ver_emis/"; + std::string mam4_soag_verti_emiss_file_name=base_path+"cmip6_mam4_soag_elev_ne2np4_2010_clim_c20190821_OD.nc"; + vert_emis_var_names_["soag"] = {"SOAbb_src","SOAbg_src", "SOAff_src"}; + vert_emis_file_name_["soag"] = mam4_soag_verti_emiss_file_name; + + + for (const auto& item : vert_emis_file_name_) { + const auto var_name = item.first; + const auto file_name = item.second; + const auto var_names = vert_emis_var_names_[var_name]; + + TracerFileType tracer_file_type; + auto hor_rem = scream::mam_coupling::create_horiz_remapper( + grid_, file_name, spa_map_file, var_names, tracer_file_type); + auto file_reader = + scream::mam_coupling::create_tracer_data_reader(hor_rem, file_name); + scream::mam_coupling::TracerData data_out, data_beg, data_end; + data_out.set_file_type(tracer_file_type); + data_beg.set_file_type(tracer_file_type); + data_end.set_file_type(tracer_file_type); + + VertEmissionsHorizInterp_.push_back(hor_rem); + VertEmissionsDataReader_.push_back(file_reader); + vert_emis_data_out_.push_back(data_out); + vert_emis_data_beg_.push_back(data_beg); + vert_emis_data_end_.push_back(data_end); + } } - } // this checks whether we have the tracers we expect -void MAMMicrophysics:: -set_computed_group_impl(const FieldGroup& group) { - const auto& name = group.m_info->m_group_name; - EKAT_REQUIRE_MSG(name=="tracers", - "Error! MAM4 expects a 'tracers' field group (got '" << name << "')\n"); +void MAMMicrophysics::set_computed_group_impl(const FieldGroup &group) { + const auto &name = group.m_info->m_group_name; + EKAT_REQUIRE_MSG( + name == "tracers", + "Error! MAM4 expects a 'tracers' field group (got '" << name << "')\n"); EKAT_REQUIRE_MSG(group.m_info->m_bundled, - "Error! MAM4 expects bundled fields for tracers.\n"); + "Error! MAM4 expects bundled fields for tracers.\n"); // how many aerosol/gas tracers do we expect? - int num_tracers = mam_coupling::num_aero_modes() + mam_coupling::num_aero_tracers() + mam_coupling::num_aero_gases(); + int num_tracers = mam_coupling::num_aero_modes() + + mam_coupling::num_aero_tracers() + + mam_coupling::num_aero_gases(); EKAT_REQUIRE_MSG(group.m_info->size() >= num_tracers, - "Error! MAM4 requires at least " << group.m_info->size()<<" "<size() << " " << num_tracers << " " + << mam_coupling::num_aero_modes() << " " + << mam_coupling::num_aero_tracers() << " " + << mam_coupling::num_aero_gases() << "aerosol tracers."); } -size_t MAMMicrophysics::requested_buffer_size_in_bytes() const -{ +size_t MAMMicrophysics::requested_buffer_size_in_bytes() const { return mam_coupling::buffer_size(ncol_, nlev_); } void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { - EKAT_REQUIRE_MSG(buffer_manager.allocated_bytes() >= requested_buffer_size_in_bytes(), - "Error! Insufficient buffer size.\n"); - - size_t used_mem = mam_coupling::init_buffer(buffer_manager, ncol_, nlev_, buffer_); - EKAT_REQUIRE_MSG(used_mem==requested_buffer_size_in_bytes(), - "Error! Used memory != requested memory for MAMMicrophysics."); + EKAT_REQUIRE_MSG( + buffer_manager.allocated_bytes() >= requested_buffer_size_in_bytes(), + "Error! Insufficient buffer size.\n"); + + size_t used_mem = + mam_coupling::init_buffer(buffer_manager, ncol_, nlev_, buffer_); + EKAT_REQUIRE_MSG( + used_mem == requested_buffer_size_in_bytes(), + "Error! Used memory != requested memory for MAMMicrophysics."); } - void MAMMicrophysics::initialize_impl(const RunType run_type) { - step_ = 0; - + // Determine orbital year. If orbital_year is negative, use current year // from timestamp for orbital year; if positive, use provided orbital year // for duration of simulation. - m_orbital_year = m_params.get("orbital_year",-9999); - + m_orbital_year = m_params.get("orbital_year", -9999); + // Get orbital parameters from yaml file - m_orbital_eccen = m_params.get("orbital_eccentricity",-9999); - m_orbital_obliq = m_params.get("orbital_obliquity" ,-9999); - m_orbital_mvelp = m_params.get("orbital_mvelp" ,-9999); + m_orbital_eccen = m_params.get("orbital_eccentricity", -9999); + m_orbital_obliq = m_params.get("orbital_obliquity", -9999); + m_orbital_mvelp = m_params.get("orbital_mvelp", -9999); // populate the wet and dry atmosphere states with views from fields and // the buffer - wet_atm_.qv = get_field_in("qv").get_view(); - wet_atm_.qc = get_field_out("qc").get_view(); - wet_atm_.nc = get_field_out("nc").get_view(); - wet_atm_.qi = get_field_in("qi").get_view(); - wet_atm_.ni = get_field_in("ni").get_view(); - - - dry_atm_.T_mid = get_field_in("T_mid").get_view(); - dry_atm_.p_mid = get_field_in("p_mid").get_view(); - dry_atm_.p_int = get_field_in("p_int").get_view(); - dry_atm_.p_del = get_field_in("pseudo_density").get_view(); - dry_atm_.cldfrac = get_field_in("cldfrac_tot").get_view(); // FIXME: tot or liq? - dry_atm_.pblh = get_field_in("pbl_height").get_view(); - dry_atm_.phis = get_field_in("phis").get_view(); - dry_atm_.omega = get_field_in("omega").get_view(); + wet_atm_.qv = get_field_in("qv").get_view(); + wet_atm_.qc = get_field_out("qc").get_view(); + wet_atm_.nc = get_field_out("nc").get_view(); + wet_atm_.qi = get_field_in("qi").get_view(); + wet_atm_.ni = get_field_in("ni").get_view(); + + dry_atm_.T_mid = get_field_in("T_mid").get_view(); + dry_atm_.p_mid = get_field_in("p_mid").get_view(); + dry_atm_.p_int = get_field_in("p_int").get_view(); + dry_atm_.p_del = get_field_in("pseudo_density").get_view(); + dry_atm_.cldfrac = get_field_in("cldfrac_tot") + .get_view(); // FIXME: tot or liq? + dry_atm_.pblh = get_field_in("pbl_height").get_view(); + dry_atm_.phis = get_field_in("phis").get_view(); + dry_atm_.omega = get_field_in("omega").get_view(); dry_atm_.z_mid = buffer_.z_mid; dry_atm_.dz = buffer_.dz; dry_atm_.z_iface = buffer_.z_iface; @@ -318,62 +410,72 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { dry_atm_.qi = buffer_.qi_dry; dry_atm_.ni = buffer_.ni_dry; dry_atm_.w_updraft = buffer_.w_updraft; - dry_atm_.z_surf = 0.0; // FIXME: for now + dry_atm_.z_surf = 0.0; // FIXME: for now + + // get surface albedo: shortwave, direct + d_sfc_alb_dir_vis_ = get_field_in("sfc_alb_dir_vis").get_view(); // perform any initialization work - if (run_type==RunType::Initial) { + if(run_type == RunType::Initial) { } // interstitial and cloudborne aerosol tracers of interest: mass (q) and // number (n) mixing ratios - for (int m = 0; m < mam_coupling::num_aero_modes(); ++m) { + for(int m = 0; m < mam_coupling::num_aero_modes(); ++m) { // interstitial aerosol tracers of interest: number (n) mixing ratios - const char* int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); - wet_aero_.int_aero_nmr[m] = get_field_out(int_nmr_field_name).get_view(); + const char *int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); + wet_aero_.int_aero_nmr[m] = + get_field_out(int_nmr_field_name).get_view(); dry_aero_.int_aero_nmr[m] = buffer_.dry_int_aero_nmr[m]; // cloudborne aerosol tracers of interest: number (n) mixing ratios - const char* cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); - wet_aero_.cld_aero_nmr[m] = get_field_out(cld_nmr_field_name).get_view(); + const char *cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); + wet_aero_.cld_aero_nmr[m] = + get_field_out(cld_nmr_field_name).get_view(); dry_aero_.cld_aero_nmr[m] = buffer_.dry_cld_aero_nmr[m]; - for (int a = 0; a < mam_coupling::num_aero_species(); ++a) { + for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { // (interstitial) aerosol tracers of interest: mass (q) mixing ratios - const char* int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); - if (strlen(int_mmr_field_name) > 0) { - wet_aero_.int_aero_mmr[m][a] = get_field_out(int_mmr_field_name).get_view(); + const char *int_mmr_field_name = + mam_coupling::int_aero_mmr_field_name(m, a); + if(strlen(int_mmr_field_name) > 0) { + wet_aero_.int_aero_mmr[m][a] = + get_field_out(int_mmr_field_name).get_view(); dry_aero_.int_aero_mmr[m][a] = buffer_.dry_int_aero_mmr[m][a]; } // (cloudborne) aerosol tracers of interest: mass (q) mixing ratios - const char* cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(m, a); + const char *cld_mmr_field_name = + mam_coupling::cld_aero_mmr_field_name(m, a); if(strlen(cld_mmr_field_name) > 0) { - wet_aero_.cld_aero_mmr[m][a] = get_field_out(cld_mmr_field_name).get_view(); + wet_aero_.cld_aero_mmr[m][a] = + get_field_out(cld_mmr_field_name).get_view(); dry_aero_.cld_aero_mmr[m][a] = buffer_.dry_cld_aero_mmr[m][a]; } } } // set wet/dry aerosol-related gas state data - for (int g = 0; g < mam_coupling::num_aero_gases(); ++g) { - const char* mmr_field_name = mam_coupling::gas_mmr_field_name(g); - wet_aero_.gas_mmr[g] = get_field_out(mmr_field_name).get_view(); + for(int g = 0; g < mam_coupling::num_aero_gases(); ++g) { + const char *mmr_field_name = mam_coupling::gas_mmr_field_name(g); + wet_aero_.gas_mmr[g] = get_field_out(mmr_field_name).get_view(); dry_aero_.gas_mmr[g] = buffer_.dry_gas_mmr[g]; } // create our photolysis rate calculation table - const std::string rsf_file = - m_params.get("mam4_rsf_file"); + const std::string rsf_file = m_params.get("mam4_rsf_file"); const std::string xs_long_file = m_params.get("mam4_xs_long_file"); - // photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); + photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); // FIXME: read relevant land use data from drydep surface file // set up our preprocess/postprocess functors - preprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, dry_aero_); - postprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, dry_aero_); + preprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, + dry_aero_); + postprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, + dry_aero_); // set field property checks for the fields in this process /* e.g. @@ -386,62 +488,72 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { */ // set up WSM for internal local variables - // (we'll probably need this later, but we'll just use ATMBufferManager for now) - //const auto default_policy = ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); - //workspace_mgr_.setup(buffer_.wsm_data, nlev_+1, 13+(n_wind_slots+n_trac_slots), default_policy); + // (we'll probably need this later, but we'll just use ATMBufferManager for + // now) + // const auto default_policy = + // ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); + // workspace_mgr_.setup(buffer_.wsm_data, nlev_+1, + // 13+(n_wind_slots+n_trac_slots), default_policy); { - // climatology data for linear stratospheric chemistry - auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] - auto linoz_o3col_clim = buffer_.scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] - auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] - auto linoz_PmL_clim = buffer_.scratch[3]; // P minus L (climatology) [vmr/s] - auto linoz_dPmL_dO3 = buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] - auto linoz_dPmL_dT = buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] - auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] - auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + // climatology data for linear stratospheric chemistry + auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] + auto linoz_o3col_clim = + buffer_.scratch[1]; // column o3 above box (climatology) [Dobson Units + // (DU)] + auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] + auto linoz_PmL_clim = + buffer_.scratch[3]; // P minus L (climatology) [vmr/s] + auto linoz_dPmL_dO3 = + buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] + auto linoz_dPmL_dT = + buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] + auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to + // overhead O3 column [vmr/DU] + auto linoz_cariolle_pscs = + buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] // Load the first month into spa_end. - // Note: At the first time step, the data will be moved into spa_beg, - // and spa_end will be reloaded from file with the new month. - const int curr_month = timestamp().get_month()-1; // 0-based - - // const std::string linoz_chlorine_file = "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; - // auto ts = timestamp(); - // int chlorine_loading_ymd=20100101; - auto ts = timestamp(); - std::string linoz_chlorine_file = - m_params.get("mam4_linoz_chlorine_file"); - int chlorine_loading_ymd = - m_params.get("mam4_chlorine_loading_ymd"); - scream::mam_coupling::create_linoz_chlorine_reader (linoz_chlorine_file, ts, chlorine_loading_ymd, - chlorine_values_, chlorine_time_secs_ ); + // Note: At the first time step, the data will be moved into spa_beg, + // and spa_end will be reloaded from file with the new month. + const int curr_month = timestamp().get_month() - 1; // 0-based + + // const std::string linoz_chlorine_file = + // "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; auto ts = + // timestamp(); int chlorine_loading_ymd=20100101; + auto ts = timestamp(); + std::string linoz_chlorine_file = + m_params.get("mam4_linoz_chlorine_file"); + int chlorine_loading_ymd = m_params.get("mam4_chlorine_loading_ymd"); + scream::mam_coupling::create_linoz_chlorine_reader( + linoz_chlorine_file, ts, chlorine_loading_ymd, chlorine_values_, + chlorine_time_secs_); } - // const int photo_table_len = get_photo_table_work_len(photo_table_); - // work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); + const int photo_table_len = get_photo_table_work_len(photo_table_); + work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); - // here's where we store per-column photolysis rates + // here's where we store per-column photolysis rates photo_rates_ = view_3d("photo_rates", ncol_, nlev_, mam4::mo_photo::phtcnt); - // liquid water cloud content - lwc_= view_2d("liquid_water_cloud_content", ncol_, nlev_); - // // Load the first month into spa_end. // Note: At the first time step, the data will be moved into spa_beg, // and spa_end will be reloaded from file with the new month. - const int curr_month = timestamp().get_month()-1; // 0-based + const int curr_month = timestamp().get_month() - 1; // 0-based { - const int nvars = 4; + const int nvars = 4; const auto io_grid = TracerHorizInterp_->get_src_grid(); - const int num_cols_io = io_grid->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io = io_grid->get_num_vertical_levels(); // Number of levels per column + const int num_cols_io = + io_grid->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io = + io_grid->get_num_vertical_levels(); // Number of levels per column tracer_data_end_.init(num_cols_io, num_levs_io, nvars); - scream::mam_coupling::update_tracer_data_from_file(TracerDataReader_, - timestamp(),curr_month, *TracerHorizInterp_, tracer_data_end_); + scream::mam_coupling::update_tracer_data_from_file( + TracerDataReader_, timestamp(), curr_month, *TracerHorizInterp_, + tracer_data_end_); tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); tracer_data_beg_.allocate_data_views(); @@ -451,105 +563,136 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { tracer_data_out_.allocate_data_views(); tracer_data_out_.allocate_ps(); - p_src_invariant_ = view_2d("pressure_src_invariant",num_cols_io, num_levs_io ); + p_src_invariant_ = + view_2d("pressure_src_invariant", num_cols_io, num_levs_io); - for (int ivar = 0; ivar< nvars; ++ivar) { - cnst_offline_[ivar] = view_2d("cnst_offline_",ncol_, nlev_ ); + for(int ivar = 0; ivar < nvars; ++ivar) { + cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); } } // linoz reader { const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); - const int num_cols_io_linoz = io_grid_linoz->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io_linoz = io_grid_linoz->get_num_vertical_levels(); // Number of levels per column + const int num_cols_io_linoz = + io_grid_linoz->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_linoz = + io_grid_linoz + ->get_num_vertical_levels(); // Number of levels per column const int nvars = 8; linoz_data_end_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); - scream::mam_coupling::update_tracer_data_from_file(LinozDataReader_, - timestamp(),curr_month, *LinozHorizInterp_, linoz_data_end_); + scream::mam_coupling::update_tracer_data_from_file( + LinozDataReader_, timestamp(), curr_month, *LinozHorizInterp_, + linoz_data_end_); linoz_data_beg_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_beg_.allocate_data_views(); - if (linoz_data_beg_.file_type == TracerFileType::FORMULA_PS){ + if(linoz_data_beg_.file_type == TracerFileType::FORMULA_PS) { linoz_data_beg_.allocate_ps(); } linoz_data_out_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_out_.allocate_data_views(); - if (linoz_data_out_.file_type == TracerFileType::FORMULA_PS) - { + if(linoz_data_out_.file_type == TracerFileType::FORMULA_PS) { linoz_data_out_.allocate_ps(); + } else if(linoz_data_out_.file_type == TracerFileType::ZONAL) { + // we use ncremap and python scripts to convert zonal files to ne4pn4 + // grids. + p_src_linoz_ = + view_2d("pressure_src_invariant", ncol_, num_levs_io_linoz); + scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_, + p_src_linoz_); } - else if (linoz_data_out_.file_type == TracerFileType::ZONAL) { - // we use ncremap and python scripts to convert zonal files to ne4pn4 grids. - p_src_linoz_ = view_2d("pressure_src_invariant",ncol_, num_levs_io_linoz ); - scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_,p_src_linoz_); - } } // vertical emissions { - // FIXME: I am assuming 1 variable per file - const int nvars = 1; - for (size_t i = 0; i < vert_emis_file_name_.size(); ++i) - { + + int i=0; + int offset_emis_ver=0; + for (const auto& item : vert_emis_file_name_) { + const auto var_name = item.first; + const auto file_name = item.second; + const auto var_names = vert_emis_var_names_[var_name]; + const int nvars = int(var_names.size()); + const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); - const int num_cols_io_emis = io_grid_emis->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io_emis = io_grid_emis->get_num_vertical_levels(); // Number of levels per column + const int num_cols_io_emis = + io_grid_emis->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_emis = + io_grid_emis + ->get_num_vertical_levels(); // Number of levels per column vert_emis_data_end_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); - scream::mam_coupling::update_tracer_data_from_file(VertEmissionsDataReader_[i], - timestamp(),curr_month, *VertEmissionsHorizInterp_[i], vert_emis_data_end_[i]); + scream::mam_coupling::update_tracer_data_from_file( + VertEmissionsDataReader_[i], timestamp(), curr_month, + *VertEmissionsHorizInterp_[i], vert_emis_data_end_[i]); vert_emis_data_beg_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); vert_emis_data_beg_[i].allocate_data_views(); - if (vert_emis_data_beg_[i].file_type == TracerFileType::FORMULA_PS){ + if(vert_emis_data_beg_[i].file_type == TracerFileType::FORMULA_PS) { vert_emis_data_beg_[i].allocate_ps(); } vert_emis_data_out_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); vert_emis_data_out_[i].allocate_data_views(); - if (vert_emis_data_out_[i].file_type == TracerFileType::FORMULA_PS) - { + if(vert_emis_data_out_[i].file_type == TracerFileType::FORMULA_PS) { vert_emis_data_out_[i].allocate_ps(); - } else if (vert_emis_data_out_[i].file_type == TracerFileType::VERT_EMISSION) { - auto zi_src = scream::mam_coupling::get_altitude_int(VertEmissionsHorizInterp_[i], - vert_emis_file_name_[i]); + } else if(vert_emis_data_out_[i].file_type == + TracerFileType::VERT_EMISSION) { + auto zi_src = scream::mam_coupling::get_altitude_int( + VertEmissionsHorizInterp_[i], file_name); vert_emis_altitude_int_.push_back(zi_src); } - const auto emis_output = view_2d("vert_emis_output_",num_cols_io_emis, num_levs_io_emis ); - vert_emis_output_.push_back(emis_output); - + for (int isp = 0; isp < nvars; ++isp) + { + EKAT_REQUIRE_MSG( + mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS <= int(offset_emis_ver), + "Error! Number of fields is bigger than MAX_NUM_VERT_EMISSION_FIELDS. Increase the MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); - }// end i + vert_emis_output_[isp+offset_emis_ver] = + view_2d("vert_emis_output_", num_cols_io_emis, num_levs_io_emis); + } + i++; + offset_emis_ver+=nvars; + } // end i } + invariants_ = view_3d("invarians", ncol_, nlev_, mam4::gas_chemistry::nfs); } void MAMMicrophysics::run_impl(const double dt) { - - const auto scan_policy = ekat::ExeSpaceUtils::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); - const auto policy = ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); + const auto scan_policy = ekat::ExeSpaceUtils< + KT::ExeSpace>::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); + const auto policy = + ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); // preprocess input -- needs a scan for the calculation of atm height Kokkos::parallel_for("preprocess", scan_policy, preprocess_); Kokkos::fence(); // reset internal WSM variables - //workspace_mgr_.reset_internals(); + // workspace_mgr_.reset_internals(); - // NOTE: nothing depends on simulation time (yet), so we can just use zero for now + // NOTE: nothing depends on simulation time (yet), so we can just use zero for + // now double t = 0.0; // climatology data for linear stratospheric chemistry - auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] - auto linoz_o3col_clim = buffer_.scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] - auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] - auto linoz_PmL_clim = buffer_.scratch[3]; // P minus L (climatology) [vmr/s] - auto linoz_dPmL_dO3 = buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] - auto linoz_dPmL_dT = buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] - auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to overhead O3 column [vmr/DU] - auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] + auto linoz_o3col_clim = + buffer_ + .scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] + auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] + auto linoz_PmL_clim = buffer_.scratch[3]; // P minus L (climatology) [vmr/s] + auto linoz_dPmL_dO3 = + buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] + auto linoz_dPmL_dT = + buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] + auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to + // overhead O3 column [vmr/DU] + auto linoz_cariolle_pscs = + buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] view_2d linoz_output[8]; linoz_output[0] = linoz_o3_clim; @@ -561,265 +704,316 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_output[6] = linoz_dPmL_dO3col; linoz_output[7] = linoz_cariolle_pscs; - - // it's a bit wasteful to store this for all columns, but simpler from an // allocation perspective auto o3_col_dens = buffer_.scratch[8]; /* Gather time and state information for interpolation */ - const auto ts = timestamp()+dt; + const auto ts = timestamp() + dt; + const Real chlorine_loading = scream::mam_coupling::chlorine_loading_advance( + ts, chlorine_values_, chlorine_time_secs_); - const Real chlorine_loading = scream::mam_coupling::chlorine_loading_advance(ts, chlorine_values_, - chlorine_time_secs_); - - - // /* Update the TracerTimeState to reflect the current time, note the addition of dt */ + // /* Update the TracerTimeState to reflect the current time, note the + // addition of dt */ linoz_time_state_.t_now = ts.frac_of_year_in_days(); // FIXME: we do not need altitude_int for invariant tracers and linoz fields. view_1d dummy_altitude_int; - scream::mam_coupling::advance_tracer_data(TracerDataReader_, - *TracerHorizInterp_, - ts, - linoz_time_state_, - tracer_data_beg_, - tracer_data_end_, - tracer_data_out_, - p_src_invariant_, - dry_atm_.p_mid, - dummy_altitude_int, - dry_atm_.z_iface, - cnst_offline_); - - scream::mam_coupling::advance_tracer_data(LinozDataReader_, - *LinozHorizInterp_, - ts, - linoz_time_state_, - linoz_data_beg_, - linoz_data_end_, - linoz_data_out_, - p_src_linoz_, - dry_atm_.p_mid, - dummy_altitude_int, - dry_atm_.z_iface, - linoz_output); - - for (size_t i = 0; i < vert_emis_file_name_.size(); ++i) + scream::mam_coupling::advance_tracer_data( + TracerDataReader_, *TracerHorizInterp_, ts, linoz_time_state_, + tracer_data_beg_, tracer_data_end_, tracer_data_out_, p_src_invariant_, + dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, cnst_offline_); + + scream::mam_coupling::advance_tracer_data( + LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, + linoz_data_beg_, linoz_data_end_, linoz_data_out_, p_src_linoz_, + dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, linoz_output); + + int i=0; + int offset_emis_ver=0; + for (const auto& item : vert_emis_file_name_) { + const auto var_name = item.first; + const auto file_name = item.second; + const auto var_names = vert_emis_var_names_[var_name]; + const int nvars = int(var_names.size()); + view_2d vert_emis_output[nvars]; + for (int isp = 0; isp < nvars; ++isp) { - // FIXME: Here I am assuming one output per file. - view_2d vert_emis_output[1]; - vert_emis_output[0] = vert_emis_output_[i]; - scream::mam_coupling::advance_tracer_data(VertEmissionsDataReader_[i], - *VertEmissionsHorizInterp_[i], - ts, - linoz_time_state_, - vert_emis_data_beg_[i], - vert_emis_data_end_[i], - vert_emis_data_out_[i], - p_src_linoz_, - dry_atm_.p_mid, - vert_emis_altitude_int_[i], - dry_atm_.z_iface, - vert_emis_output); + vert_emis_output[isp]= vert_emis_output_[isp+offset_emis_ver]; } - const_view_1d &col_latitudes = col_latitudes_; - mam_coupling::DryAtmosphere &dry_atm = dry_atm_; - mam_coupling::AerosolState &dry_aero = dry_aero_; + offset_emis_ver+=nvars; + scream::mam_coupling::advance_tracer_data( + VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, + linoz_time_state_, vert_emis_data_beg_[i], vert_emis_data_end_[i], + vert_emis_data_out_[i], p_src_linoz_, dry_atm_.p_mid, + vert_emis_altitude_int_[i], dry_atm_.z_iface, vert_emis_output); + i++; + } + const_view_1d &col_latitudes = col_latitudes_; + const_view_1d &col_longitudes = col_longitudes_; + const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; + + mam_coupling::DryAtmosphere &dry_atm = dry_atm_; + mam_coupling::AerosolState &dry_aero = dry_aero_; mam4::mo_photo::PhotoTableData &photo_table = photo_table_; - const int nlev = nlev_; - const Config &config = config_; - const auto& step= step_; + const int nlev = nlev_; + const Config &config = config_; + const auto &step = step_; // FIXME: read relevant linoz climatology data from file(s) based on time // FIXME: read relevant chlorine loading data from file based on time - const auto& work_photo_table = work_photo_table_; - const auto& photo_rates = photo_rates_; - - const auto& lwc =lwc_; + const auto &work_photo_table = work_photo_table_; + const auto &photo_rates = photo_rates_; + const auto &invariants = invariants_; + const auto &cnst_offline = cnst_offline_; // Compute orbital parameters; these are used both for computing // the solar zenith angle. - auto ts = timestamp(); + auto ts2 = timestamp(); double obliqr, lambm0, mvelpp; auto orbital_year = m_orbital_year; - auto eccen = m_orbital_eccen; - auto obliq = m_orbital_obliq; - auto mvelp = m_orbital_mvelp; - if (eccen >= 0 && obliq >= 0 && mvelp >= 0) { + auto eccen = m_orbital_eccen; + auto obliq = m_orbital_obliq; + auto mvelp = m_orbital_mvelp; + if(eccen >= 0 && obliq >= 0 && mvelp >= 0) { // use fixed orbital parameters; to force this, we need to set // orbital_year to SHR_ORB_UNDEF_INT, which is exposed through // our c2f bridge as shr_orb_undef_int_c2f orbital_year = shr_orb_undef_int_c2f; - } else if (orbital_year < 0) { + } else if(orbital_year < 0) { // compute orbital parameters based on current year - orbital_year = ts.get_year(); + orbital_year = ts2.get_year(); } - shr_orb_params_c2f(&orbital_year, &eccen, &obliq, &mvelp, - &obliqr, &lambm0, &mvelpp); - // Use the orbital parameters to calculate the solar declination and eccentricity factor - double delta, eccf; - auto calday = ts.frac_of_year_in_days() + 1; // Want day + fraction; calday 1 == Jan 1 0Z - shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, - obliqr, &delta, &eccf); - + shr_orb_params_c2f(&orbital_year, &eccen, &obliq, &mvelp, &obliqr, &lambm0, + &mvelpp); + // Use the orbital parameters to calculate the solar declination and + // eccentricity factor + Real delta, eccf; + auto calday = ts2.frac_of_year_in_days() + + 1; // Want day + fraction; calday 1 == Jan 1 0Z + shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, obliqr, &delta, &eccf); + constexpr int num_modes = mam4::AeroConfig::num_modes(); + constexpr int gas_pcnst = mam_coupling::gas_pcnst(); + constexpr int nqtendbb = mam_coupling::nqtendbb(); + + // FIXME: I believe Balwinder add this array somewhere in mam4xx or eamxx. + const Real adv_mass[gas_pcnst] = { + 47.998200, 34.013600, 98.078400, 64.064800, 62.132400, + 12.011000, 115.107340, 12.011000, 12.011000, 12.011000, + 135.064039, 58.442468, 250092.672000, 1.007400, 115.107340, + 12.011000, 58.442468, 250092.672000, 1.007400, 135.064039, + 58.442468, 115.107340, 12.011000, 12.011000, 12.011000, + 250092.672000, 1.007400, 12.011000, 12.011000, 250092.672000, + 1.007400}; + constexpr int pcnst = mam4::pcnst; // loop over atmosphere columns and compute aerosol microphyscs - Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const ThreadTeam& team) { - const int icol = team.league_rank(); // column index - - Real col_lat = col_latitudes(icol); // column latitude (degrees?) - - // fetch column-specific atmosphere state data - auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); - auto z_iface = ekat::subview(dry_atm.z_iface, icol); - Real phis = dry_atm.phis(icol); - - // liquid water cloud content - const auto& lwc_icol = ekat::subview(lwc, icol); - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { - lwc_icol(k) = atm.ice_mixing_ratio(k) + atm.liquid_mixing_ratio(k); - }); - team.team_barrier(); - - // set surface state data - haero::Surface sfc{}; - - // fetch column-specific subviews into aerosol prognostics - mam4::Prognostics progs = mam_coupling::aerosols_for_column(dry_aero, icol); - - // set up diagnostics - mam4::Diagnostics diags(nlev); - - // calculate o3 column densities (first component of col_dens in Fortran code) - auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); - impl::compute_o3_column_density(team, atm, progs, o3_col_dens_i); - - // set up photolysis work arrays for this column. - mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; - // FIXME: set views here - // const auto& work_photo_table_icol = ekat::subview(work_photo_table, icol); - // set work view using 1D photo_work_arrays_icol - - // mam4::mo_photo::set_photo_table_work_arrays(photo_table, - // work_photo_table_icol, - // photo_work_arrays_icol); - // ... look up photolysis rates from our table - // NOTE: the table interpolation operates on an entire column of data, so we - // NOTE: must do it before dispatching to individual vertical levels - Real zenith_angle = 0.0; // FIXME: need to get this from EAMxx [radians] - Real surf_albedo = 0.0; // FIXME: surface albedo - Real esfact = 0.0; // FIXME: earth-sun distance factor - - const auto& photo_rates_icol = ekat::subview(photo_rates, icol); - mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, atm.hydrostatic_dp, - atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, atm.liquid_mixing_ratio, - atm.cloud_fraction, esfact, photo_table, photo_work_arrays_icol); - - // compute aerosol microphysics on each vertical level within this column - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev), [&](const int k) { - - constexpr int num_modes = mam4::AeroConfig::num_modes(); - constexpr int gas_pcnst = mam_coupling::gas_pcnst(); - constexpr int nqtendbb = mam_coupling::nqtendbb(); - - // extract atm state variables (input) - Real temp = atm.temperature(k); - Real pmid = atm.pressure(k); - Real pdel = atm.hydrostatic_dp(k); - Real zm = atm.height(k); - Real zi = z_iface(k); - Real pblh = atm.planetary_boundary_layer_height; - Real qv = atm.vapor_mixing_ratio(k); - Real cldfrac = atm.cloud_fraction(k); - Real lwc = atm.liquid_mixing_ratio(k); - Real cldnum = atm.cloud_liquid_number_mixing_ratio(k); - - // extract aerosol state variables into "working arrays" (mass mixing ratios) - // (in EAM, this is done in the gas_phase_chemdr subroutine defined within - // mozart/mo_gas_phase_chemdr.F90) - Real q[gas_pcnst] = {}; - Real qqcw[gas_pcnst] = {}; - mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); - - // convert mass mixing ratios to volume mixing ratios (VMR), equivalent - // to tracer mixing ratios (TMR)) - Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); - - // aerosol/gas species tendencies (output) - Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; - Real vmrcw_tendbb[gas_pcnst][nqtendbb] = {}; - - // create work array copies to retain "pre-chemistry" values - Real vmr_pregaschem[gas_pcnst] = {}; - Real vmr_precldchem[gas_pcnst] = {}; - Real vmrcw_precldchem[gas_pcnst] = {}; - for (int i = 0; i < gas_pcnst; ++i) { - vmr_pregaschem[i] = vmr[i]; - vmr_precldchem[i] = vmr[i]; - vmrcw_precldchem[i] = vmrcw[i]; - } + Kokkos::parallel_for( + policy, KOKKOS_LAMBDA(const ThreadTeam &team) { + const int icol = team.league_rank(); // column index + + Real col_lat = col_latitudes(icol); // column latitude (degrees?) + Real col_lon = col_longitudes(icol); // column longitude + + Real rlats = + col_lat * M_PI / 180.0; // convert column latitude to radians + Real rlons = + col_lon * M_PI / 180.0; // convert column longitude to radians + + // fetch column-specific atmosphere state data + auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); + auto z_iface = ekat::subview(dry_atm.z_iface, icol); + Real phis = dry_atm.phis(icol); + + // set surface state data + haero::Surface sfc{}; + + // fetch column-specific subviews into aerosol prognostics + mam4::Prognostics progs = + mam_coupling::aerosols_for_column(dry_aero, icol); + + // set up diagnostics + mam4::Diagnostics diags(nlev); + + // + auto invariants_icol = ekat::subview(invariants, icol); + + view_1d cnst_offline_icol[mam4::mo_setinv::num_tracer_cnst]; + for (int i = 0; i < mam4::mo_setinv::num_tracer_cnst; ++i) { + cnst_offline_icol[i] = ekat::subview(cnst_offline[i],icol); + } + + mam4::mo_setinv::setinv(team, invariants_icol, atm.temperature, + atm.vapor_mixing_ratio, + cnst_offline_icol, atm.pressure); + + // calculate o3 column densities (first component of col_dens in Fortran + // code) + auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); + impl::compute_o3_column_density(team, atm, progs, invariants_icol, + o3_col_dens_i); + + // set up photolysis work arrays for this column. + mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; + const auto& work_photo_table_icol = ekat::subview(work_photo_table, + icol); + // set work view using 1D photo_work_arrays_icol + mam4::mo_photo::set_photo_table_work_arrays(photo_table, + work_photo_table_icol, + photo_work_arrays_icol); + + // ... look up photolysis rates from our table + // NOTE: the table interpolation operates on an entire column of data, + // so we NOTE: must do it before dispatching to individual vertical + // levels + Real zenith_angle = + shr_orb_cosz_c2f(calday, rlats, rlons, delta, + dt); // what's the aerosol microphys frequency? + zenith_angle = acos(zenith_angle); + + Real surf_albedo = d_sfc_alb_dir_vis(icol); + + const auto &photo_rates_icol = ekat::subview(photo_rates, icol); + + mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, + atm.hydrostatic_dp, + atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, + atm.liquid_mixing_ratio, atm.cloud_fraction, eccf, photo_table, + photo_work_arrays_icol); + + // compute aerosol microphysics on each vertical level within this + // column + Kokkos::parallel_for( + Kokkos::TeamThreadRange(team, nlev), [&](const int k) { + // extract atm state variables (input) + Real temp = atm.temperature(k); + Real pmid = atm.pressure(k); + Real pdel = atm.hydrostatic_dp(k); + Real zm = atm.height(k); + Real zi = z_iface(k); + Real pblh = atm.planetary_boundary_layer_height; + Real qv = atm.vapor_mixing_ratio(k); + Real cldfrac = atm.cloud_fraction(k); + Real lwc = atm.liquid_mixing_ratio(k); + Real cldnum = atm.cloud_liquid_number_mixing_ratio(k); + + // extract aerosol state variables into "working arrays" (mass + // mixing ratios) (in EAM, this is done in the gas_phase_chemdr + // subroutine defined within + // mozart/mo_gas_phase_chemdr.F90) + Real q[gas_pcnst] = {}; + Real qqcw[gas_pcnst] = {}; + // mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, + // qqcw); + Real state_q[pcnst] = {}; + Real qqcw_long[pcnst] = {}; + mam4::utils::extract_stateq_from_prognostics(progs,atm, state_q, k); + + // std::cout << "Before state_q: "; + // for (int i = 0; i < pcnst; ++i) { + // std::cout << state_q[i] << " "; + // } + + mam4::utils::extract_qqcw_from_prognostics(progs,qqcw_long,k); + // FIXME: remove this hard-code value + const int offset_aerosol = 9; + for (int i = offset_aerosol; i < pcnst; ++i) { + q[i-offset_aerosol] =state_q[i]; + qqcw[i-offset_aerosol] =qqcw_long[i]; + } + // convert mass mixing ratios to volume mixing ratios (VMR), + // equivalent to tracer mixing ratios (TMR)) + Real vmr[gas_pcnst], vmrcw[gas_pcnst]; + // CHECK: convert_work_arrays_to_vmr and mmr2vmr should produce the same ouputs + // However, in mmr2vmr we do not iterate to get species value. + // mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); + mam_coupling::mmr2vmr(q,adv_mass, vmr); + mam_coupling::mmr2vmr(qqcw,adv_mass,vmrcw); + + // aerosol/gas species tendencies (output) + Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; + Real vmrcw_tendbb[gas_pcnst][nqtendbb] = {}; + + // create work array copies to retain "pre-chemistry" values + Real vmr_pregaschem[gas_pcnst] = {}; + Real vmr_precldchem[gas_pcnst] = {}; + Real vmrcw_precldchem[gas_pcnst] = {}; + for(int i = 0; i < gas_pcnst; ++i) { + vmr_pregaschem[i] = vmr[i]; + vmr_precldchem[i] = vmr[i]; + vmrcw_precldchem[i] = vmrcw[i]; + } + + //--------------------- + // Gas Phase Chemistry + //--------------------- + // + Real photo_rates_k[mam4::mo_photo::phtcnt]; + for(int i = 0; i < mam4::mo_photo::phtcnt; ++i) { + photo_rates_k[i] = photo_rates_icol(k, i); + } + + Real extfrc_k[mam4::gas_chemistry::extcnt]; + for (int i = 0; i < mam4::gas_chemistry::extcnt; ++i) { + //extfrc_k[i] = extfrc_icol(k, i); + extfrc_k[i] = 0.0; + } + + Real invariants_k[mam4::gas_chemistry::nfs]; + for(int i = 0; i < mam4::gas_chemistry::nfs; ++i) { + invariants_k[i] = invariants_icol(k, i); + } + + impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, + photo_rates_k, extfrc_k, invariants_k, vmr); + + //---------------------- + // Aerosol microphysics + //---------------------- + // the logic below is taken from the aero_model_gasaerexch + // subroutine in eam/src/chemistry/modal_aero/aero_model.F90 + + // aqueous chemistry ... + const int loffset = + 8; // offset of first tracer in work arrays + // (taken from mam4xx setsox validation test) + const Real mbar = haero::Constants::molec_weight_dry_air; + constexpr int indexm = mam4::gas_chemistry::indexm; + mam4::mo_setsox::setsox_single_level( + loffset, dt, pmid, pdel, temp, mbar, lwc, cldfrac, cldnum, + invariants_k[indexm], config.setsox, vmrcw, vmr); + + // calculate aerosol water content using water uptake treatment + // * dry and wet diameters [m] + // * wet densities [kg/m3] + // * aerosol water mass mixing ratio [kg/kg] + Real dgncur_a[num_modes] = {}; + Real dgncur_awet[num_modes] = {}; + Real wetdens[num_modes] = {}; + Real qaerwat[num_modes] = {}; + + impl::compute_water_content(progs, atm, k, qv, temp, pmid, dgncur_a, + dgncur_awet, wetdens, qaerwat); + + // do aerosol microphysics (gas-aerosol exchange, nucleation, + // coagulation) + impl::modal_aero_amicphys_intr( + config.amicphys, step, dt, temp, pmid, pdel, zm, pblh, qv, + cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, + vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a, + dgncur_awet, wetdens, qaerwat); + //----------------- + // LINOZ chemistry + //----------------- + + // the following things are diagnostics, which we're not + // including in the first rev + Real do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, + o3clim_linoz_diag, zenith_angle_degrees; + + int o3_ndx = 0; // index of "O3" in solsym array (in EAM) - //--------------------- - // Gas Phase Chemistry - //--------------------- - // - Real photo_rates_k[mam4::mo_photo::phtcnt]; - for (int i = 0; i < mam4::mo_photo::phtcnt; ++i) { - photo_rates_k[i] = photo_rates_icol(k, i); - } - constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" - // NOTE: we compute invariants here and pass them out to use later with - // NOTE: setsox - Real invariants[nfs]; - impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k, vmr, invariants); - - //---------------------- - // Aerosol microphysics - //---------------------- - // the logic below is taken from the aero_model_gasaerexch subroutine in - // eam/src/chemistry/modal_aero/aero_model.F90 - - // aqueous chemistry ... - const int loffset = 8; // offset of first tracer in work arrays - // (taken from mam4xx setsox validation test) - const Real mbar = haero::Constants::molec_weight_dry_air; - constexpr int indexm = 0; // FIXME: index of xhnm in invariants array (??) - /*mam4::mo_setsox::setsox_single_level(loffset, dt, pmid, pdel, temp, mbar, lwc, - cldfrac, cldnum, invariants[indexm], config.setsox, vmrcw, vmr);*/ - - // calculate aerosol water content using water uptake treatment - // * dry and wet diameters [m] - // * wet densities [kg/m3] - // * aerosol water mass mixing ratio [kg/kg] - Real dgncur_a[num_modes] = {}; - Real dgncur_awet[num_modes] = {}; - Real wetdens[num_modes] = {}; - Real qaerwat[num_modes] = {}; - //impl::compute_water_content(progs, k, qv, temp, pmid, dgncur_a, dgncur_awet, wetdens, qaerwat); - - // do aerosol microphysics (gas-aerosol exchange, nucleation, coagulation) - /*impl::modal_aero_amicphys_intr(config.amicphys, step, dt, t, pmid, pdel, - zm, pblh, qv, cldfrac, vmr, vmrcw, vmr_pregaschem, - vmr_precldchem, vmrcw_precldchem, vmr_tendbb, - vmrcw_tendbb, dgncur_a, dgncur_awet, - wetdens, qaerwat); */ - - //----------------- - // LINOZ chemistry - //----------------- - - // the following things are diagnostics, which we're not - // including in the first rev - Real do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, - zenith_angle_degrees; - - Real rlats = col_lat * M_PI / 180.0; // convert column latitude to radians - int o3_ndx = 0; // index of "O3" in solsym array (in EAM) -#if 0 mam4::lin_strat_chem::lin_strat_chem_solve_kk(o3_col_dens_i(k), temp, zenith_angle, pmid, dt, rlats, linoz_o3_clim(icol, k), linoz_t_clim(icol, k), linoz_o3col_clim(icol, k), @@ -828,38 +1022,53 @@ void MAMMicrophysics::run_impl(const double dt) { chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees); -#endif - // update source terms above the ozone decay threshold - /*if (k > nlev - config.linoz.o3_lbl - 1) { - Real do3_mass; // diagnostic, not needed - mam4::lin_strat_chem::lin_strat_sfcsink_kk(dt, pdel, vmr[o3_ndx], config.linoz.o3_sfc, - config.linoz.o3_tau, do3_mass); - }*/ - - // ... check for negative values and reset to zero - for (int i = 0; i < gas_pcnst; ++i) { - if (vmr[i] < 0.0) vmr[i] = 0.0; - } - //---------------------- - // Dry deposition (gas) - //---------------------- - - // FIXME: C++ port in progress! - //mam4::drydep::drydep_xactive(...); - - // transfer updated prognostics from work arrays - //mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); - //mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, progs, k); - }); - }); + // update source terms above the ozone decay threshold + /*if (k > nlev - config.linoz.o3_lbl - 1) { + Real do3_mass; // diagnostic, not needed + mam4::lin_strat_chem::lin_strat_sfcsink_kk(dt, pdel, + vmr[o3_ndx], config.linoz.o3_sfc, config.linoz.o3_tau, do3_mass); + }*/ + + // ... check for negative values and reset to zero + for(int i = 0; i < gas_pcnst; ++i) { + if(vmr[i] < 0.0) vmr[i] = 0.0; + } + + //---------------------- + // Dry deposition (gas) + //---------------------- + + // FIXME: C++ port in progress! + // mam4::drydep::drydep_xactive(...); + + mam_coupling::vmr2mmr(vmr,adv_mass, q); + mam_coupling::vmr2mmr(vmrcw,adv_mass,qqcw); + + for (int i = offset_aerosol; i < pcnst; ++i) { + state_q[i] = q[i-offset_aerosol]; + qqcw_long[i] = qqcw[i-offset_aerosol]; + } + // mam4::utils::inject_stateq_to_prognostics(state_q,progs,k); + // std::cout << "state_q: "; + // for (int i = 0; i < pcnst; ++i) { + // std::cout << state_q[i] << " "; + // } + // std::cout << std::endl; + + mam4::utils::inject_qqcw_to_prognostics(qqcw_long, progs,k); + // transfer updated prognostics from work arrays + // mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); + // mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, + // progs, k); + }); + }); // postprocess output Kokkos::parallel_for("postprocess", policy, postprocess_); Kokkos::fence(); } -void MAMMicrophysics::finalize_impl() { -} +void MAMMicrophysics::finalize_impl() {} -} // namespace scream +} // namespace scream diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 89d76e3a1d7..97a63b00e97 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -230,6 +230,9 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // column areas, latitudes, longitudes const_view_1d col_areas_, col_latitudes_, col_longitudes_; + // surface albedo: shortwave, direct + const_view_1d d_sfc_alb_dir_vis_; + // time step number int step_; @@ -248,7 +251,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::vector chlorine_values_; std::vector chlorine_time_secs_; view_3d photo_rates_; - view_2d lwc_; // invariants members std::shared_ptr TracerDataReader_; @@ -257,6 +259,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::TracerData tracer_data_beg_; mam_coupling::TracerData tracer_data_out_; view_2d p_src_invariant_; + view_3d invariants_; std::string oxid_file_name_; view_2d cnst_offline_[4]; @@ -275,12 +278,10 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::vector vert_emis_data_end_; std::vector vert_emis_data_beg_; std::vector vert_emis_data_out_; - std::vector vert_emis_file_name_; std::vector vert_emis_altitude_int_; - std::vector vert_emis_output_; - - - + std::map< std::string, std::string >vert_emis_file_name_; + std::map< std::string, std::vector > vert_emis_var_names_; + view_2d vert_emis_output_[mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS]; diff --git a/components/eamxx/src/physics/mam/impl/README.md b/components/eamxx/src/physics/mam/impl/README.md index f05a484a82a..1983ed0c53d 100644 --- a/components/eamxx/src/physics/mam/impl/README.md +++ b/components/eamxx/src/physics/mam/impl/README.md @@ -1,4 +1,4 @@ -# MAM4 Integration Code +#MAM4 Integration Code This folder contains C++ implementations of the higher-level MAM4 interface routines for aerosol microphysics, cloud-aerosol interactions, and optical diff --git a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp index ceb992bc886..825c861fff6 100644 --- a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp +++ b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp @@ -1,43 +1,46 @@ namespace scream::impl { -KOKKOS_INLINE_FUNCTION -void compute_o3_column_density(const ThreadTeam& team, const haero::Atmosphere& atm, - const mam4::Prognostics &progs, ColumnView o3_col_dens) { - constexpr int gas_pcnst = mam4::gas_chemistry::gas_pcnst; // number of gas phase species - constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" - // constexpr Real mwdry = 1.0/haero::Constants::molec_weight_dry_air; +using View2D = DeviceType::view_2d; - Real o3_col_deltas[mam4::nlev+1] = {}; // o3 column density above model [1/cm^2] +KOKKOS_INLINE_FUNCTION +void compute_o3_column_density(const ThreadTeam &team, + const haero::Atmosphere &atm, + const mam4::Prognostics &progs, + const View2D &invariants, + ColumnView o3_col_dens) { + constexpr int gas_pcnst = + mam4::gas_chemistry::gas_pcnst; // number of gas phase species + constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" + + Real o3_col_deltas[mam4::nlev + 1] = + {}; // o3 column density above model [1/cm^2] // NOTE: if we need o2 column densities, set_ub_col and setcol must be changed - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, atm.num_levels()), [&](const int k) { - - // Real temp = atm.temperature(k); - // Real pmid = atm.pressure(k); - Real pdel = atm.hydrostatic_dp(k); - // Real qv = atm.vapor_mixing_ratio(k); - - // ... map incoming mass mixing ratios to working array - Real q[gas_pcnst], qqcw[gas_pcnst]; - mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); - - // ... set atmosphere mean mass to the molecular weight of dry air - // and compute water vapor vmr - // Real mbar = mwdry; - // Real h2ovmr = mam4::conversions::vmr_from_mmr(qv, mbar); - - // ... Xform from mmr to vmr - Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); - - // ... compute invariants for this level - Real invariants[nfs]; - // setinv(invariants, temp, h2ovmr, vmr, pmid); FIXME: not yet ported - - // compute the change in o3 density for this column above its neighbor - mam4::mo_photo::set_ub_col(o3_col_deltas[k+1], vmr, invariants, pdel); - }); + Kokkos::parallel_for( + Kokkos::TeamThreadRange(team, atm.num_levels()), [&](const int k) { + Real temp = atm.temperature(k); + Real pmid = atm.pressure(k); + Real pdel = atm.hydrostatic_dp(k); + + // ... map incoming mass mixing ratios to working array + Real q[gas_pcnst], qqcw[gas_pcnst]; + mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); + + // ... Xform from mmr to vmr + Real vmr[gas_pcnst], vmrcw[gas_pcnst]; + mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); + + // ... compute invariants for this level + Real invariants_k[nfs]; + for(int i = 0; i < nfs; ++i) { + invariants_k[i] = invariants(k, i); + } + + // compute the change in o3 density for this column above its neighbor + mam4::mo_photo::set_ub_col(o3_col_deltas[k + 1], vmr, invariants_k, + pdel); + }); // sum the o3 column deltas to densities mam4::mo_photo::setcol(o3_col_deltas, o3_col_dens); } -} // namespace scream::impl +} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp b/components/eamxx/src/physics/mam/impl/compute_water_content.cpp index ea7190afff9..d651e860387 100644 --- a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp +++ b/components/eamxx/src/physics/mam/impl/compute_water_content.cpp @@ -3,13 +3,14 @@ namespace scream::impl { KOKKOS_INLINE_FUNCTION -void compute_water_content(const mam4::Prognostics &progs, int k, - Real qv, Real temp, Real pmid, +void compute_water_content(const mam4::Prognostics &progs, + const haero::Atmosphere &atm, int k, Real qv, + Real temp, Real pmid, Real dgncur_a[mam4::AeroConfig::num_modes()], Real dgncur_awet[mam4::AeroConfig::num_modes()], Real wetdens[mam4::AeroConfig::num_modes()], Real qaerwat[mam4::AeroConfig::num_modes()]) { - constexpr int num_modes = mam4::AeroConfig::num_modes(); + constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int num_aero_ids = mam4::AeroConfig::num_aerosol_ids(); // get some information about aerosol species @@ -18,82 +19,71 @@ void compute_water_content(const mam4::Prognostics &progs, int k, int nspec_amode[num_modes], lspectype_amode[maxd_aspectype][num_modes]; Real specdens_amode[maxd_aspectype], spechygro[maxd_aspectype]; mam4::water_uptake::get_e3sm_parameters(nspec_amode, lspectype_amode, - specdens_amode, spechygro); + specdens_amode, spechygro); // extract aerosol tracers for this level into state_q, which is needed // for computing dry aerosol properties below // FIXME: we should eliminate this index translation stuff - constexpr int nvars = aero_model::pcnst; - Real state_q[nvars]; // aerosol tracers for level k - for (int imode = 0; imode < num_modes; ++imode) { - int la, lc; // interstitial and cloudborne indices within state_q - // number mixing ratios - mam4::convproc::assign_la_lc(imode, -1, la, lc); - state_q[la] = progs.n_mode_i[imode](k); - state_q[lc] = progs.n_mode_c[imode](k); - // aerosol mass mixing ratios - for (int iaero = 0; iaero < num_aero_ids; ++iaero) { - mam4::convproc::assign_la_lc(imode, iaero, la, lc); - auto mode = static_cast(imode); - auto aero = static_cast(iaero); - int ispec = mam4::aerosol_index_for_mode(mode, aero); - if (ispec != -1) { - state_q[la] = progs.q_aero_i[imode][ispec](k); - state_q[lc] = progs.q_aero_c[imode][ispec](k); - } - } - } + constexpr int nvars = aero_model::pcnst; + Real state_q[nvars]; // aerosol tracers for level k + mam4::utils::extract_stateq_from_prognostics(progs, + atm,state_q, + k); // compute the dry volume for each mode, and from it the current dry // geometric nominal particle diameter. // FIXME: We have to do some gymnastics here to set up the calls to // FIXME: calcsize. This could be improved. Real inv_densities[num_modes][num_aero_ids] = {}; - for (int imode = 0; imode < num_modes; ++imode) { + for(int imode = 0; imode < num_modes; ++imode) { const int n_spec = mam4::num_species_mode(imode); - for (int ispec = 0; ispec < n_spec; ++ispec) { + for(int ispec = 0; ispec < n_spec; ++ispec) { const int iaer = static_cast(mam4::mode_aero_species(imode, ispec)); - const Real density = mam4::aero_species(iaer).density; + const Real density = mam4::aero_species(iaer).density; inv_densities[imode][ispec] = 1.0 / density; } } - for (int imode = 0; imode < num_modes; ++imode) { - Real dryvol_i, dryvol_c; // interstitial and cloudborne dry volumes + for(int imode = 0; imode < num_modes; ++imode) { + Real dryvol_i, dryvol_c; // interstitial and cloudborne dry volumes mam4::calcsize::compute_dry_volume_k(k, imode, inv_densities, progs, - dryvol_i, dryvol_c); + dryvol_i, dryvol_c); // NOTE: there's some disagreement over whether vol2num should be called // NOTE: num2vol here, so I'm just adopting the nomenclature used by // NOTE: the following call to calcsize) - const mam4::Mode& mode = mam4::modes(imode); - Real vol2num_min = 1.0/mam4::conversions::mean_particle_volume_from_diameter( - mode.max_diameter, mode.mean_std_dev); - Real vol2num_max = 1.0/mam4::conversions::mean_particle_volume_from_diameter( - mode.min_diameter, mode.mean_std_dev); + const mam4::Mode &mode = mam4::modes(imode); + Real vol2num_min = + 1.0 / mam4::conversions::mean_particle_volume_from_diameter( + mode.max_diameter, mode.mean_std_dev); + Real vol2num_max = + 1.0 / mam4::conversions::mean_particle_volume_from_diameter( + mode.min_diameter, mode.mean_std_dev); Real vol2num; - mam4::calcsize::update_diameter_and_vol2num(dryvol_i, - progs.n_mode_i[imode](k), vol2num_min, vol2num_max, + mam4::calcsize::update_diameter_and_vol2num( + dryvol_i, progs.n_mode_i[imode](k), vol2num_min, vol2num_max, mode.min_diameter, mode.max_diameter, mode.mean_std_dev, dgncur_a[imode], vol2num); } // calculate dry aerosol properties - Real hygro[num_modes], naer[num_modes], dryrad[num_modes], - dryvol[num_modes], drymass[num_modes], - rhcrystal[num_modes], rhdeliques[num_modes], specdens_1[num_modes]; - mam4::water_uptake::modal_aero_water_uptake_dryaer(nspec_amode, specdens_amode, - spechygro, lspectype_amode, state_q, dgncur_a, hygro, - naer, dryrad, dryvol, drymass, rhcrystal, rhdeliques, specdens_1); + Real hygro[num_modes], naer[num_modes], dryrad[num_modes], dryvol[num_modes], + drymass[num_modes], rhcrystal[num_modes], rhdeliques[num_modes], + specdens_1[num_modes]; + mam4::water_uptake::modal_aero_water_uptake_dryaer( + nspec_amode, specdens_amode, spechygro, lspectype_amode, state_q, + dgncur_a, hygro, naer, dryrad, dryvol, drymass, rhcrystal, rhdeliques, + specdens_1); // calculate wet aerosol properties - Real rh = mam4::conversions::relative_humidity_from_vapor_mixing_ratio(qv, temp, pmid); + Real rh = mam4::conversions::relative_humidity_from_vapor_mixing_ratio( + qv, temp, pmid); Real wetrad[num_modes], wetvol[num_modes], wtrvol[num_modes]; - mam4::water_uptake::modal_aero_water_uptake_wetaer(rhcrystal, rhdeliques, dgncur_a, - dryrad, hygro, rh, naer, dryvol, wetrad, wetvol, wtrvol, dgncur_awet, - qaerwat); - mam4::water_uptake::modal_aero_water_uptake_wetdens(wetvol, wtrvol, - drymass, specdens_1, wetdens); + mam4::water_uptake::modal_aero_water_uptake_wetaer( + rhcrystal, rhdeliques, dgncur_a, dryrad, hygro, rh, naer, dryvol, wetrad, + wetvol, wtrvol, dgncur_awet, qaerwat); + mam4::water_uptake::modal_aero_water_uptake_wetdens(wetvol, wtrvol, drymass, + specdens_1, wetdens); } -} // namespace scream::impl +} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index dbba6393429..f7828cf8470 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -20,26 +20,33 @@ using HostViewInt1D = haero::DeviceType::view_1d::HostMirror; int nc_dimension(const char *file, int nc_id, const char *dim_name) { int dim_id; int result = nc_inq_dimid(nc_id, dim_name, &dim_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " << dim_name << - " dimension ID from NetCDF file '" << file << "'\n"); + EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " + << dim_name + << " dimension ID from NetCDF file '" + << file << "'\n"); size_t dim; result = nc_inq_dimlen(nc_id, dim_id, &dim); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " << dim_name << - " dimension from NetCDF file '" << file << "'\n"); + EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " + << dim_name + << " dimension from NetCDF file '" << file + << "'\n"); return static_cast(dim); } // ON HOST (MPI root rank only), reads data from the given NetCDF variable from // the file with the given ID into the given Kokkos host View template -void read_nc_var(const char *file, int nc_id, const char *var_name, V host_view) { +void read_nc_var(const char *file, int nc_id, const char *var_name, + V host_view) { int var_id; int result = nc_inq_varid(nc_id, var_name, &var_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch ID for variable '" << var_name << - "' from NetCDF file '" << file << "'\n"); + EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch ID for variable '" + << var_name << "' from NetCDF file '" + << file << "'\n"); result = nc_get_var(nc_id, var_id, host_view.data()); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't read data for variable '" << var_name << - "' from NetCDF file '" << file << "'\n"); + EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't read data for variable '" + << var_name << "' from NetCDF file '" + << file << "'\n"); } // ON HOST (MPI root rank only), reads data from the NetCDF variable with the @@ -47,8 +54,9 @@ void read_nc_var(const char *file, int nc_id, const char *var_name, V host_view) template void read_nc_var(const char *file, int nc_id, int var_id, V host_view) { int result = nc_get_var(nc_id, var_id, host_view.data()); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't read data for variable with ID " << - var_id << " from NetCDF file '" << file << "'\n"); + EKAT_REQUIRE_MSG(result == 0, + "Error! Couldn't read data for variable with ID " + << var_id << " from NetCDF file '" << file << "'\n"); } // ON HOST (MPI root only), sets the lng_indexer and pht_alias_mult_1 host views @@ -59,18 +67,22 @@ void set_lng_indexer_and_pht_alias_mult_1(const char *file, int nc_id, // NOTE: it seems that the chemical mechanism we're using // NOTE: 1. sets pht_alias_lst to a blank string [1] // NOTE: 2. sets pht_alias_mult_1 to 1.0 [1] - // NOTE: 3. sets rxt_tag_lst to ['jh2o2', 'usr_HO2_HO2', 'usr_SO2_OH', 'usr_DMS_OH'] [2] - // NOTE: References: - // NOTE: [1] (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L117) - // NOTE: [2] (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L99) - - // populate lng_indexer (see https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/mozart/mo_jlong.F90#L180) - static const char *var_names[4] = {"jh2o2", "usr_HO2_HO2", "usr_SO2_OH", "usr_DMS_OH"}; - for (int m = 0; m < mam4::mo_photo::phtcnt; ++m) { + // NOTE: 3. sets rxt_tag_lst to ['jh2o2', 'usr_HO2_HO2', 'usr_SO2_OH', + // 'usr_DMS_OH'] [2] NOTE: References: NOTE: [1] + // (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L117) + // NOTE: [2] + // (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L99) + + // populate lng_indexer (see + // https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/mozart/mo_jlong.F90#L180) + static const char *var_names[4] = {"jh2o2", "usr_HO2_HO2", "usr_SO2_OH", + "usr_DMS_OH"}; + for(int m = 0; m < mam4::mo_photo::phtcnt; ++m) { int var_id; int result = nc_inq_varid(nc_id, var_names[m], &var_id); EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch ID for variable '" - << var_names[m] << "' from NetCDF file '" << file << "'\n"); + << var_names[m] << "' from NetCDF file '" + << file << "'\n"); lng_indexer(m) = var_id; } @@ -82,7 +94,8 @@ void set_lng_indexer_and_pht_alias_mult_1(const char *file, int nc_id, // solar data from our solar_data_file void populate_etfphot(HostView1D we, HostView1D etfphot) { // FIXME: It looks like EAM is relying on a piece of infrastructure that - // FIXME: we just don't have in EAMxx (eam/src/chemistry/utils/solar_data.F90). + // FIXME: we just don't have in EAMxx + // (eam/src/chemistry/utils/solar_data.F90). // FIXME: I have no idea whether EAMxx has a plan for supporting this // FIXME: solar irradiance / photon flux data, and I'm not going to recreate // FIXME: that capability here. So this is an unplugged hole. @@ -94,112 +107,140 @@ void populate_etfphot(HostView1D we, HostView1D etfphot) { // FIXME: zero the photon flux for now Kokkos::deep_copy(etfphot, 0); } -// This version uses scream_scorpio_interface to read netcdf files. -mam4::mo_photo::PhotoTableData read_photo_table(const std::string& rsf_file, - const std::string& xs_long_file) { - - -// set up the lng_indexer and pht_alias_mult_1 views based on our -// (hardwired) chemical mechanism -HostViewInt1D lng_indexer_h("lng_indexer(host)", mam4::mo_photo::phtcnt); +std::vector populate_etfphot_from_e3sm_case() +{ + // We obtained these values from an e3sm simulations. + // We should only use this function on Host. + std::vector etfphot_data = { + 7.5691227E+11, 8.6525905E+11, 1.0355749E+12, 1.1846288E+12, 2.1524405E+12, + 3.2362584E+12, 3.7289849E+12, 4.4204330E+12, 4.6835350E+12, 6.1217728E+12, + 4.5575051E+12, 5.3491446E+12, 4.7016063E+12, 5.4281722E+12, 4.5023968E+12, + 6.8931981E+12, 6.2012647E+12, 6.1430771E+12, 5.7820385E+12, 7.6770646E+12, + 1.3966509E+13, 1.2105348E+13, 2.8588980E+13, 3.2160821E+13, 2.4978066E+13, + 2.7825401E+13, 2.3276451E+13, 3.6343684E+13, 6.1787886E+13, 7.8009914E+13, + 7.6440824E+13, 7.6291458E+13, 9.4645085E+13, 1.0124628E+14, 1.0354111E+14, + 1.0999650E+14, 1.0889946E+14, 1.1381912E+14, 1.3490042E+14, 1.5941519E+14, + 1.4983265E+14, 1.5184267E+14, 1.5991420E+14, 1.6976697E+14, 1.8771840E+14, + 1.6434367E+14, 1.8371960E+14, 2.1966369E+14, 1.9617879E+14, 2.2399700E+14, + 1.8429912E+14, 2.0129736E+14, 2.0541588E+14, 2.4334962E+14, 3.5077122E+14, + 3.4517894E+14, 3.5749668E+14, 3.6624304E+14, 3.4975113E+14, 3.5566025E+14, + 4.2825273E+14, 4.8406375E+14, 4.9511159E+14, 5.2695368E+14, 5.2401611E+14, + 5.0877746E+14, 4.8780853E+14 + }; + return etfphot_data; +} +// This version uses scream_scorpio_interface to read netcdf files. +mam4::mo_photo::PhotoTableData read_photo_table( + const std::string &rsf_file, const std::string &xs_long_file) { + // set up the lng_indexer and pht_alias_mult_1 views based on our + // (hardwired) chemical mechanism + HostViewInt1D lng_indexer_h("lng_indexer(host)", mam4::mo_photo::phtcnt); -int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions -scorpio::register_file(rsf_file,scorpio::Read); -// read and broadcast dimension data -nump = scorpio::get_dimlen(rsf_file,"numz"); -numsza = scorpio::get_dimlen(rsf_file, "numsza"); -numalb = scorpio::get_dimlen(rsf_file, "numalb"); -numcolo3 = scorpio::get_dimlen(rsf_file, "numcolo3fact"); + int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions + scorpio::register_file(rsf_file, scorpio::Read); + // read and broadcast dimension data + nump = scorpio::get_dimlen(rsf_file, "numz"); + numsza = scorpio::get_dimlen(rsf_file, "numsza"); + numalb = scorpio::get_dimlen(rsf_file, "numalb"); + numcolo3 = scorpio::get_dimlen(rsf_file, "numcolo3fact"); + + scorpio::register_file(xs_long_file, scorpio::Read); + nt = scorpio::get_dimlen(xs_long_file, "numtemp"); + nw = scorpio::get_dimlen(xs_long_file, "numwl"); + np_xs = scorpio::get_dimlen(xs_long_file, "numprs"); + + // FIXME: hard-coded for only one photo reaction. + std::string rxt_names[1] = {"jh2o2"}; + int numj = 1; + lng_indexer_h(0) = 0; + // allocate the photolysis table + auto table = mam4::mo_photo::create_photo_table_data( + nw, nt, np_xs, numj, nump, numsza, numcolo3, numalb); -scorpio::register_file(xs_long_file,scorpio::Read); -nt = scorpio::get_dimlen(xs_long_file, "numtemp"); -nw = scorpio::get_dimlen(xs_long_file, "numwl"); -np_xs = scorpio::get_dimlen(xs_long_file, "numprs"); + // allocate host views for table data + auto rsf_tab_h = Kokkos::create_mirror_view(table.rsf_tab); + auto xsqy_h = Kokkos::create_mirror_view(table.xsqy); + auto sza_h = Kokkos::create_mirror_view(table.sza); + auto alb_h = Kokkos::create_mirror_view(table.alb); + auto press_h = Kokkos::create_mirror_view(table.press); + auto colo3_h = Kokkos::create_mirror_view(table.colo3); + auto o3rat_h = Kokkos::create_mirror_view(table.o3rat); + // auto etfphot_h = Kokkos::create_mirror_view(table.etfphot); + auto prs_h = Kokkos::create_mirror_view(table.prs); + + // read file data into our host views + scorpio::read_var(rsf_file, "pm", press_h.data()); + scorpio::read_var(rsf_file, "sza", sza_h.data()); + scorpio::read_var(rsf_file, "alb", alb_h.data()); + scorpio::read_var(rsf_file, "colo3fact", o3rat_h.data()); + scorpio::read_var(rsf_file, "colo3", colo3_h.data()); + // it produces an error. + scorpio::read_var(rsf_file, "RSF", rsf_tab_h.data()); + scorpio::read_var(xs_long_file, "pressure", prs_h.data()); + + // read xsqy data (using lng_indexer_h for the first index) + // FIXME: hard-coded for only one photo reaction. + for(int m = 0; m < mam4::mo_photo::phtcnt; ++m) { + auto xsqy_ndx_h = ekat::subview(xsqy_h, m); + scorpio::read_var(xs_long_file, rxt_names[m], xsqy_h.data()); + } -//FIXME: hard-coded for only one photo reaction. -std::string rxt_names[1] = {"jh2o2"}; -int numj = 1; -lng_indexer_h(0)=0; -// allocate the photolysis table -auto table = mam4::mo_photo::create_photo_table_data(nw, nt, np_xs, numj, - nump, numsza, numcolo3, - numalb); + // populate etfphot by rebinning solar data + HostView1D wc_h("wc", nw), wlintv_h("wlintv", nw), we_h("we", nw + 1); -// allocate host views for table data -auto rsf_tab_h = Kokkos::create_mirror_view(table.rsf_tab); -auto xsqy_h = Kokkos::create_mirror_view(table.xsqy); -auto sza_h = Kokkos::create_mirror_view(table.sza); -auto alb_h = Kokkos::create_mirror_view(table.alb); -auto press_h = Kokkos::create_mirror_view(table.press); -auto colo3_h = Kokkos::create_mirror_view(table.colo3); -auto o3rat_h = Kokkos::create_mirror_view(table.o3rat); -auto etfphot_h = Kokkos::create_mirror_view(table.etfphot); -auto prs_h = Kokkos::create_mirror_view(table.prs); - -// read file data into our host views -scorpio::read_var(rsf_file, "pm", press_h.data()); -scorpio::read_var(rsf_file, "sza", sza_h.data()); -scorpio::read_var(rsf_file, "alb", alb_h.data()); -scorpio::read_var(rsf_file, "colo3fact", o3rat_h.data()); -scorpio::read_var(rsf_file, "colo3", colo3_h.data()); -// it produces an error. -scorpio::read_var(rsf_file, "RSF", rsf_tab_h.data()); -scorpio::read_var(xs_long_file, "pressure", prs_h.data()); - -// read xsqy data (using lng_indexer_h for the first index) -//FIXME: hard-coded for only one photo reaction. -for (int m = 0; m < mam4::mo_photo::phtcnt; ++m) { - auto xsqy_ndx_h = ekat::subview(xsqy_h, m); - scorpio::read_var(xs_long_file, rxt_names[m], xsqy_h.data()); -} + scorpio::read_var(rsf_file, "wc", wc_h.data()); + scorpio::read_var(rsf_file, "wlintv", wlintv_h.data()); + for(int i = 0; i < nw; ++i) { + we_h(i) = wc_h(i) - 0.5 * wlintv_h(i); + } + we_h(nw) = wc_h(nw - 1) - 0.5 * wlintv_h(nw - 1); + // populate_etfphot(we_h, etfphot_h); + // FIXME: etfphot_data is hard-coded. + auto etfphot_data = populate_etfphot_from_e3sm_case(); + auto etfphot_h = HostView1D((Real *)etfphot_data.data(),nw); -// populate etfphot by rebinning solar data -HostView1D wc_h("wc", nw), wlintv_h("wlintv", nw), we_h("we", nw+1); + scorpio::release_file(rsf_file); + scorpio::release_file(xs_long_file); -scorpio::read_var(rsf_file, "wc", wc_h.data()); -scorpio::read_var(rsf_file, "wlintv", wlintv_h.data()); -for (int i = 0; i < nw; ++i) { - we_h(i) = wc_h(i) - 0.5 * wlintv_h(i); -} -we_h(nw) = wc_h(nw-1) - 0.5 * wlintv_h(nw-1); -populate_etfphot(we_h, etfphot_h); -scorpio::release_file(rsf_file); -scorpio::release_file(xs_long_file); - -// copy host photolysis table into place on device -Kokkos::deep_copy(table.rsf_tab, rsf_tab_h); -Kokkos::deep_copy(table.xsqy, xsqy_h); -Kokkos::deep_copy(table.sza, sza_h); -Kokkos::deep_copy(table.alb, alb_h); -Kokkos::deep_copy(table.press, press_h); -Kokkos::deep_copy(table.colo3, colo3_h); -Kokkos::deep_copy(table.o3rat, o3rat_h); -Kokkos::deep_copy(table.etfphot, etfphot_h); -Kokkos::deep_copy(table.prs, prs_h); -// set pht_alias_mult_1 to 1 -Kokkos::deep_copy(table.pht_alias_mult_1, 1.0); -Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); + // copy host photolysis table into place on device + Kokkos::deep_copy(table.rsf_tab, rsf_tab_h); + Kokkos::deep_copy(table.xsqy, xsqy_h); + Kokkos::deep_copy(table.sza, sza_h); + Kokkos::deep_copy(table.alb, alb_h); + Kokkos::deep_copy(table.press, press_h); + Kokkos::deep_copy(table.colo3, colo3_h); + Kokkos::deep_copy(table.o3rat, o3rat_h); + Kokkos::deep_copy(table.etfphot, etfphot_h); + Kokkos::deep_copy(table.prs, prs_h); + // set pht_alias_mult_1 to 1 + Kokkos::deep_copy(table.pht_alias_mult_1, 1.0); + Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); // compute gradients (on device) - Kokkos::parallel_for("del_p", nump-1, KOKKOS_LAMBDA(int i) { - table.del_p(i) = 1.0/::abs(table.press(i)- table.press(i+1)); - }); - Kokkos::parallel_for("del_sza", numsza-1, KOKKOS_LAMBDA(int i) { - table.del_sza(i) = 1.0/(table.sza(i+1) - table.sza(i)); - }); - Kokkos::parallel_for("del_alb", numalb-1, KOKKOS_LAMBDA(int i) { - table.del_alb(i) = 1.0/(table.alb(i+1) - table.alb(i)); - }); - Kokkos::parallel_for("del_o3rat", numcolo3-1, KOKKOS_LAMBDA(int i) { - table.del_o3rat(i) = 1.0/(table.o3rat(i+1) - table.o3rat(i)); - }); - Kokkos::parallel_for("dprs", np_xs-1, KOKKOS_LAMBDA(int i) { - table.dprs(i) = 1.0/(table.prs(i) - table.prs(i+1)); - }); + Kokkos::parallel_for( + "del_p", nump - 1, KOKKOS_LAMBDA(int i) { + table.del_p(i) = 1.0 / haero::abs(table.press(i) - table.press(i + 1)); + }); + Kokkos::parallel_for( + "del_sza", numsza - 1, KOKKOS_LAMBDA(int i) { + table.del_sza(i) = 1.0 / (table.sza(i + 1) - table.sza(i)); + }); + Kokkos::parallel_for( + "del_alb", numalb - 1, KOKKOS_LAMBDA(int i) { + table.del_alb(i) = 1.0 / (table.alb(i + 1) - table.alb(i)); + }); + Kokkos::parallel_for( + "del_o3rat", numcolo3 - 1, KOKKOS_LAMBDA(int i) { + table.del_o3rat(i) = 1.0 / (table.o3rat(i + 1) - table.o3rat(i)); + }); + Kokkos::parallel_for( + "dprs", np_xs - 1, KOKKOS_LAMBDA(int i) { + table.dprs(i) = 1.0 / (table.prs(i) - table.prs(i + 1)); + }); -return table; + return table; } #if 0 @@ -369,31 +410,41 @@ mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, // performs gas phase chemistry calculations on a single level of a single // atmospheric column KOKKOS_INLINE_FUNCTION -void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real pdel, Real dt, - const Real photo_rates[mam4::mo_photo::phtcnt], // in - Real q[mam4::gas_chemistry::gas_pcnst], // VMRs, inout - Real invariants[mam4::gas_chemistry::nfs]) { // out +void gas_phase_chemistry( + Real zm, Real zi, Real phis, Real temp, Real pmid, Real pdel, Real dt, + const Real photo_rates[mam4::mo_photo::phtcnt], // in + const Real extfrc[mam4::gas_chemistry::extcnt], // in + Real invariants[mam4::gas_chemistry::nfs], // in + Real q[mam4::gas_chemistry::gas_pcnst]) { // VMRs, inout // constexpr Real rga = 1.0/haero::Constants::gravity; // constexpr Real m2km = 0.01; // converts m -> km - // The following things are chemical mechanism dependent! See mam4xx/src/mam4xx/gas_chem_mechanism.hpp) - constexpr int gas_pcnst = mam4::gas_chemistry::gas_pcnst; // number of gas phase species - constexpr int rxntot = mam4::gas_chemistry::rxntot; // number of chemical reactions - constexpr int indexm = mam4::gas_chemistry::indexm; // index of total atm density in invariant array - - constexpr int phtcnt = mam4::mo_photo::phtcnt; // number of photolysis reactions + // The following things are chemical mechanism dependent! See + // mam4xx/src/mam4xx/gas_chem_mechanism.hpp) + constexpr int gas_pcnst = + mam4::gas_chemistry::gas_pcnst; // number of gas phase species + constexpr int rxntot = + mam4::gas_chemistry::rxntot; // number of chemical reactions + constexpr int extcnt = + mam4::gas_chemistry::extcnt; // number of species with external forcing + constexpr int indexm = + mam4::gas_chemistry::indexm; // index of total atm density in invariant + // array + + constexpr int phtcnt = + mam4::mo_photo::phtcnt; // number of photolysis reactions constexpr int itermax = mam4::gas_chemistry::itermax; constexpr int clscnt4 = mam4::gas_chemistry::clscnt4; - constexpr int nfs = mam4::gas_chemistry::nfs; + constexpr int nfs = mam4::gas_chemistry::nfs; // NOTE: vvv these arrays were copied from mam4xx/gas_chem_mechanism.hpp vvv constexpr int permute_4[gas_pcnst] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; - constexpr int clsmap_4[gas_pcnst] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; + constexpr int clsmap_4[gas_pcnst] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; // These indices for species are fixed by the chemical mechanism // std::string solsym[] = {"O3", "H2O2", "H2SO4", "SO2", "DMS", "SOAG", @@ -403,8 +454,10 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // "so4_a3", "bc_a3", "pom_a3", "soa_a3", "mom_a3", // "num_a3", "pom_a4", "bc_a4", "mom_a4", "num_a4"}; constexpr int ndx_h2so4 = 2; + // Q: note that "num_a3" is not part of this list in the e3sm refactored code. // std::string extfrc_list[] = {"SO2", "so4_a1", "so4_a2", "pom_a4", "bc_a4", - // "num_a1", "num_a2", "num_a3", "num_a4", "SOAG"}; + // "num_a1", "num_a2", "num_a3", "num_a4", + // "SOAG"}; constexpr int synoz_ndx = -1; // fetch the zenith angle (not its cosine!) in degrees for this column. @@ -416,13 +469,6 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // Real zsurf = rga * phis; // Real zmid = m2km * (zm + zsurf); - // ... compute the column's invariants - // Real h2ovmr = q[0]; - // setinv(invariants, temp, h2ovmr, q, pmid); FIXME: not ported yet - for (int i = 0; i < nfs; ++i) { - invariants[i] = 0.1; - } - // ... set rates for "tabular" and user specified reactions Real reaction_rates[rxntot]; mam4::gas_chemistry::setrxt(reaction_rates, temp); @@ -430,25 +476,31 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // set reaction rates based on chemical invariants // (indices (ndxes?) are taken from mam4 validation data and translated from // 1-based indices to 0-based indices) - int usr_HO2_HO2_ndx = 1, usr_DMS_OH_ndx = 5, - usr_SO2_OH_ndx = 3, inv_h2o_ndx = 3; - mam4::gas_chemistry::usrrxt(reaction_rates, temp, invariants, invariants[indexm], - usr_HO2_HO2_ndx, usr_DMS_OH_ndx, - usr_SO2_OH_ndx, inv_h2o_ndx); + int usr_HO2_HO2_ndx = 1, usr_DMS_OH_ndx = 5, usr_SO2_OH_ndx = 3, + inv_h2o_ndx = 3; + mam4::gas_chemistry::usrrxt(reaction_rates, temp, invariants, + invariants[indexm], usr_HO2_HO2_ndx, + usr_DMS_OH_ndx, usr_SO2_OH_ndx, inv_h2o_ndx); mam4::gas_chemistry::adjrxt(reaction_rates, invariants, invariants[indexm]); //=================================== // Photolysis rates at time = t(n+1) //=================================== + // compute the rate of change from forcing + Real extfrc_rates[extcnt]; // [1/cm^3/s] + for (int mm = 0; mm < extcnt; ++mm) { + if (mm != synoz_ndx) { + extfrc_rates[mm] = extfrc[mm] / invariants[indexm]; + } + } // ... Form the washout rates Real het_rates[gas_pcnst]; // FIXME: not ported yet - //sethet(het_rates, pmid, zmid, phis, temp, cmfdqr, prain, nevapr, delt, + // sethet(het_rates, pmid, zmid, phis, temp, cmfdqr, prain, nevapr, delt, // invariants[indexm], q); - // save h2so4 before gas phase chem (for later new particle nucleation) Real del_h2so4_gasprod = q[ndx_h2so4]; @@ -456,14 +508,15 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // Class solution algorithms //=========================== - // copy photolysis rates into reaction_rates (assumes photolysis rates come first) - for (int i = 0; i < phtcnt; ++i) { + // copy photolysis rates into reaction_rates (assumes photolysis rates come + // first) + for(int i = 0; i < phtcnt; ++i) { reaction_rates[i] = photo_rates[i]; } // ... solve for "Implicit" species bool factor[itermax]; - for (int i = 0; i < itermax; ++i) { + for(int i = 0; i < itermax; ++i) { factor[i] = true; } @@ -473,13 +526,13 @@ void gas_phase_chemistry(Real zm, Real zi, Real phis, Real temp, Real pmid, Real // solve chemical system implicitly Real prod_out[clscnt4], loss_out[clscnt4]; - mam4::gas_chemistry::imp_sol(q, reaction_rates, het_rates, dt, - permute_4, clsmap_4, factor, epsilon, prod_out, loss_out); + mam4::gas_chemistry::imp_sol(q, reaction_rates, het_rates, extfrc_rates, dt, permute_4, + clsmap_4, factor, epsilon, prod_out, loss_out); // save h2so4 change by gas phase chem (for later new particle nucleation) - if (ndx_h2so4 > 0) { + if(ndx_h2so4 > 0) { del_h2so4_gasprod = q[ndx_h2so4] - del_h2so4_gasprod; } } -} // namespace scream::impl +} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 0215541f71f..1ac8096f79a 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -1,355 +1,342 @@ #ifndef EAMXX_MAM_HELPER_MICRO #define EAMXX_MAM_HELPER_MICRO -#include "share/io/scorpio_input.hpp" -#include "share/io/scream_scorpio_interface.hpp" +#include +#include + +#include "share/grid/point_grid.hpp" #include "share/grid/remap/coarsening_remapper.hpp" -#include "share/grid/remap/refining_remapper_p2p.hpp" #include "share/grid/remap/identity_remapper.hpp" -#include "share/grid/point_grid.hpp" -#include -#include +#include "share/grid/remap/refining_remapper_p2p.hpp" +#include "share/io/scorpio_input.hpp" +#include "share/io/scream_scorpio_interface.hpp" namespace scream::mam_coupling { - using namespace ShortFieldTagsNames; - using view_1d_host = typename KT::view_1d::HostMirror; - - using ExeSpace = typename KT::ExeSpace; - using ESU = ekat::ExeSpaceUtils; - using C = scream::physics::Constants; - using LIV = ekat::LinInterp; - - enum TracerFileType{ - // file with PS ncol, lev, and time - FORMULA_PS, - // nc zonal file from ncremap - ZONAL, - // vertical emission files - VERT_EMISSION, - }; - - /* Maximum number of tracers (or fields) that the tracer reader can handle. - Note: We are not allocating memory for MAX_NVARS_TRACER tracers. - Therefore, if a file contains more than this number, it is acceptable to increase this limit. - Currently, Linoz files have 8 fields. */ - constexpr int MAX_NVARS_TRACER=10; - - // Linoz structures to help manage all of the variables: - struct TracerTimeState { - // Whether the timestate has been initialized. - // The current month - int current_month = -1; - // Julian Date for the beginning of the month, as defined in - // /src/share/util/scream_time_stamp.hpp - // See this file for definition of Julian Date. - Real t_beg_month; - // Current simulation Julian Date - Real t_now; - // Number of days in the current month, cast as a Real - Real days_this_month; - }; // TricerTimeState - - struct TracerData{ - TracerData() = default; - TracerData(const int ncol, const int nlev, const int nvars) - { - init (ncol,nlev, nvars); - } - void init (const int ncol, - const int nlev, - const int nvars){ - ncol_=ncol; - nlev_=nlev; - nvars_=nvars; - EKAT_REQUIRE_MSG (nvars_ <= int(MAX_NVARS_TRACER), - "Error! Number of variables is bigger than NVARS_MAXTRACER. \n"); - } +using namespace ShortFieldTagsNames; +using view_1d_host = typename KT::view_1d::HostMirror; + +using ExeSpace = typename KT::ExeSpace; +using ESU = ekat::ExeSpaceUtils; +using C = scream::physics::Constants; +using LIV = ekat::LinInterp; + +enum TracerFileType { + // file with PS ncol, lev, and time + FORMULA_PS, + // nc zonal file from ncremap + ZONAL, + // vertical emission files + VERT_EMISSION, +}; + +/* Maximum number of tracers (or fields) that the tracer reader can handle. + Note: We are not allocating memory for MAX_NVARS_TRACER tracers. + Therefore, if a file contains more than this number, it is acceptable to + increase this limit. Currently, Linoz files have 8 fields. */ +constexpr int MAX_NVARS_TRACER = 10; +constexpr int MAX_NUM_VERT_EMISSION_FIELDS = 25; + +// Linoz structures to help manage all of the variables: +struct TracerTimeState { + // Whether the timestate has been initialized. + // The current month + int current_month = -1; + // Julian Date for the beginning of the month, as defined in + // /src/share/util/scream_time_stamp.hpp + // See this file for definition of Julian Date. + Real t_beg_month; + // Current simulation Julian Date + Real t_now; + // Number of days in the current month, cast as a Real + Real days_this_month; +}; // TricerTimeState + +struct TracerData { + TracerData() = default; + TracerData(const int ncol, const int nlev, const int nvars) { + init(ncol, nlev, nvars); + } + void init(const int ncol, const int nlev, const int nvars) { + ncol_ = ncol; + nlev_ = nlev; + nvars_ = nvars; + EKAT_REQUIRE_MSG( + nvars_ <= int(MAX_NVARS_TRACER), + "Error! Number of variables is bigger than NVARS_MAXTRACER. \n"); + } - int ncol_{-1}; - int nlev_{-1}; - int nvars_{-1}; - // We cannot use a std::vector - // because we need to access these views from device. - view_2d data[MAX_NVARS_TRACER]; - view_1d ps; - const_view_1d hyam; - const_view_1d hybm; - - TracerFileType file_type; - - void allocate_data_views() - { - EKAT_REQUIRE_MSG (ncol_ != int(-1), - "Error! ncols has not been set. \n"); - EKAT_REQUIRE_MSG (nlev_ !=int(-1), - "Error! nlevs has not been set. \n"); - - for (int ivar = 0; ivar< nvars_; ++ivar) { - data[ivar] = view_2d("linoz_1",ncol_,nlev_); - } - } //allocate_data_views + int ncol_{-1}; + int nlev_{-1}; + int nvars_{-1}; + // We cannot use a std::vector + // because we need to access these views from device. + view_2d data[MAX_NVARS_TRACER]; + view_1d ps; + const_view_1d hyam; + const_view_1d hybm; - void set_file_type (const TracerFileType file_type_in) - { - file_type=file_type_in; - } + TracerFileType file_type; - void allocate_ps() - { - EKAT_REQUIRE_MSG (ncol_ != int(-1), - "Error! ncols has not been set. \n"); - EKAT_REQUIRE_MSG (file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); + void allocate_data_views() { + EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); + EKAT_REQUIRE_MSG(nlev_ != int(-1), "Error! nlevs has not been set. \n"); - ps = view_1d("ps",ncol_); + for(int ivar = 0; ivar < nvars_; ++ivar) { + data[ivar] = view_2d("linoz_1", ncol_, nlev_); } + } // allocate_data_views - void set_data_views(view_2d list_of_views[]) - { - for (int ivar = 0; ivar< nvars_; ++ivar) { + void set_file_type(const TracerFileType file_type_in) { + file_type = file_type_in; + } + + void allocate_ps() { + EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); + EKAT_REQUIRE_MSG(file_type == FORMULA_PS, + "Error! file does have the PS variable. \n"); + + ps = view_1d("ps", ncol_); + } + + void set_data_views(view_2d list_of_views[]) { + for(int ivar = 0; ivar < nvars_; ++ivar) { EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, - "Error! Insufficient memory size.\n"); - data[ivar] =list_of_views[ivar]; - } + "Error! Insufficient memory size.\n"); + data[ivar] = list_of_views[ivar]; } + } - void set_data_ps(const view_1d& ps_in) - { - EKAT_REQUIRE_MSG (file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); - ps = ps_in; - } + void set_data_ps(const view_1d &ps_in) { + EKAT_REQUIRE_MSG(file_type == FORMULA_PS, + "Error! file does have the PS variable. \n"); + ps = ps_in; + } - void set_hyam_n_hybm(const std::shared_ptr& horiz_remapper, - const std::string& tracer_file_name) - { - EKAT_REQUIRE_MSG (file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); - - // Read in hyam/hybm in start/end data - auto nondim = ekat::units::Units::nondimensional(); - const auto io_grid = horiz_remapper->get_src_grid(); - Field hyam_f(FieldIdentifier("hyam",io_grid->get_vertical_layout(true),nondim,io_grid->name())); - Field hybm_f(FieldIdentifier("hybm",io_grid->get_vertical_layout(true),nondim,io_grid->name())); - hyam_f.allocate_view(); - hybm_f.allocate_view(); - AtmosphereInput hvcoord_reader(tracer_file_name,io_grid,{hyam_f,hybm_f},true); - hvcoord_reader.read_variables(); - hvcoord_reader.finalize(); - hyam = hyam_f.get_view(); - hybm = hyam_f.get_view(); - } + void set_hyam_n_hybm(const std::shared_ptr &horiz_remapper, + const std::string &tracer_file_name) { + EKAT_REQUIRE_MSG(file_type == FORMULA_PS, + "Error! file does have the PS variable. \n"); + + // Read in hyam/hybm in start/end data + auto nondim = ekat::units::Units::nondimensional(); + const auto io_grid = horiz_remapper->get_src_grid(); + Field hyam_f(FieldIdentifier("hyam", io_grid->get_vertical_layout(true), + nondim, io_grid->name())); + Field hybm_f(FieldIdentifier("hybm", io_grid->get_vertical_layout(true), + nondim, io_grid->name())); + hyam_f.allocate_view(); + hybm_f.allocate_view(); + AtmosphereInput hvcoord_reader(tracer_file_name, io_grid, {hyam_f, hybm_f}, + true); + hvcoord_reader.read_variables(); + hvcoord_reader.finalize(); + hyam = hyam_f.get_view(); + hybm = hyam_f.get_view(); + } +}; - }; - - inline - const_view_1d get_altitude_int(const std::shared_ptr& horiz_remapper, - const std::string& tracer_file_name) - { - // Read in hyam/hybm in start/end data - auto nondim = ekat::units::Units::nondimensional(); - const auto io_grid = horiz_remapper->get_src_grid(); - Field altitude_int_f(FieldIdentifier("altitude_int",io_grid->get_vertical_layout(false),nondim,io_grid->name())); - altitude_int_f.allocate_view(); - AtmosphereInput hvcoord_reader(tracer_file_name,io_grid,{altitude_int_f},true); - hvcoord_reader.read_variables(); - hvcoord_reader.finalize(); - return altitude_int_f.get_view(); - }// set_altitude_int +inline const_view_1d get_altitude_int( + const std::shared_ptr &horiz_remapper, + const std::string &tracer_file_name) { + // Read in hyam/hybm in start/end data + auto nondim = ekat::units::Units::nondimensional(); + const auto io_grid = horiz_remapper->get_src_grid(); + Field altitude_int_f(FieldIdentifier("altitude_int", + io_grid->get_vertical_layout(false), + nondim, io_grid->name())); + altitude_int_f.allocate_view(); + AtmosphereInput hvcoord_reader(tracer_file_name, io_grid, {altitude_int_f}, + true); + hvcoord_reader.read_variables(); + hvcoord_reader.finalize(); + return altitude_int_f.get_view(); +} // set_altitude_int // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp // FIXME: I need to convert for loops to Kokkos loops. KOKKOS_INLINE_FUNCTION -void vert_interp(int ncol, - int levsiz, - int pver, - const view_2d& pin, - const const_view_2d& pmid, - const view_2d& datain, - const view_2d& dataout, - //work array - const view_int_1d& kupper - ) -{ - const int one = 1; - // Initialize index array - for (int i = 0; i < ncol; ++i) { - kupper(i)= one; - } // ncol - - for (int k = 0; k < pver; ++k) { - // Top level we need to start looking is the top level for the previous k for all column points - int kkstart = levsiz; - for (int i = 0; i < ncol; ++i) { - kkstart = haero::min(kkstart, kupper(i)); - } +void vert_interp(int ncol, int levsiz, int pver, const view_2d &pin, + const const_view_2d &pmid, const view_2d &datain, + const view_2d &dataout, + // work array + const view_int_1d &kupper) { + const int one = 1; + // Initialize index array + for(int i = 0; i < ncol; ++i) { + kupper(i) = one; + } // ncol + + for(int k = 0; k < pver; ++k) { + // Top level we need to start looking is the top level for the previous k + // for all column points + int kkstart = levsiz; + for(int i = 0; i < ncol; ++i) { + kkstart = haero::min(kkstart, kupper(i)); + } - // Store level indices for interpolation - for (int kk = kkstart - 1; kk < levsiz - 1; ++kk) { - for (int i = 0; i < ncol; ++i) { - if (pin(i, kk) < pmid(i, k) && pmid(i, k) <= pin(i, kk + 1)) { - kupper(i) = kk; - }// end if - } // end for - } // end kk - // Interpolate or extrapolate... - for (int i = 0; i < ncol; ++i) { - if (pmid(i, k) < pin(i, 0)) { - dataout(i, k) = datain(i, 0) * pmid(i, k) / pin(i, 0); - } else if (pmid(i, k) > pin(i, levsiz - 1)) { - dataout(i, k) = datain(i, levsiz - 1); - } else { - Real dpu = pmid(i, k) - pin(i, kupper(i)); - Real dpl = pin(i, kupper(i) + 1) - pmid(i, k); - dataout(i, k) = (datain(i, kupper(i)) * dpl + datain(i, kupper(i) + 1) * dpu) / (dpl + dpu); - }// end if - } // end col - } // end k - -} // vert_interp + // Store level indices for interpolation + for(int kk = kkstart - 1; kk < levsiz - 1; ++kk) { + for(int i = 0; i < ncol; ++i) { + if(pin(i, kk) < pmid(i, k) && pmid(i, k) <= pin(i, kk + 1)) { + kupper(i) = kk; + } // end if + } // end for + } // end kk + // Interpolate or extrapolate... + for(int i = 0; i < ncol; ++i) { + if(pmid(i, k) < pin(i, 0)) { + dataout(i, k) = datain(i, 0) * pmid(i, k) / pin(i, 0); + } else if(pmid(i, k) > pin(i, levsiz - 1)) { + dataout(i, k) = datain(i, levsiz - 1); + } else { + Real dpu = pmid(i, k) - pin(i, kupper(i)); + Real dpl = pin(i, kupper(i) + 1) - pmid(i, k); + dataout(i, k) = + (datain(i, kupper(i)) * dpl + datain(i, kupper(i) + 1) * dpu) / + (dpl + dpu); + } // end if + } // end col + } // end k + +} // vert_interp KOKKOS_INLINE_FUNCTION -Real linear_interp(const Real& x0, const Real& x1, const Real& t) -{ - return (1 - t)*x0 + t*x1; -} // linear_interp - - // time[3]={year,month, day} - inline util::TimeStamp convert_date(const int date) - { - constexpr int ten_thousand = 10000; - constexpr int one_hundred = 100; - - int year = date / ten_thousand; - int month = (date-year*ten_thousand)/one_hundred; - int day = date-year*ten_thousand-month*one_hundred; - return util::TimeStamp(year,month,day,0,0,0); - } - // FIXME: check if this function is implemented in eamxx - // Assumes 365 days/year, 30 days/month - inline int compute_days(const util::TimeStamp& ts ) - { - return ts.get_year()*365 + ts.get_month()*30 + ts.get_day(); - } +Real linear_interp(const Real &x0, const Real &x1, const Real &t) { + return (1 - t) * x0 + t * x1; +} // linear_interp + +// time[3]={year,month, day} +inline util::TimeStamp convert_date(const int date) { + constexpr int ten_thousand = 10000; + constexpr int one_hundred = 100; + + int year = date / ten_thousand; + int month = (date - year * ten_thousand) / one_hundred; + int day = date - year * ten_thousand - month * one_hundred; + return util::TimeStamp(year, month, day, 0, 0, 0); +} +// FIXME: check if this function is implemented in eamxx +// Assumes 365 days/year, 30 days/month +inline int compute_days(const util::TimeStamp &ts) { + return ts.get_year() * 365 + ts.get_month() * 30 + ts.get_day(); +} - inline void create_linoz_chlorine_reader( - const std::string& linoz_chlorine_file, - const util::TimeStamp& model_time, - const int chlorine_loading_ymd, // in format YYYYMMDD - std::vector& values, - std::vector& time_secs - ) - { +inline void create_linoz_chlorine_reader( + const std::string &linoz_chlorine_file, const util::TimeStamp &model_time, + const int chlorine_loading_ymd, // in format YYYYMMDD + std::vector &values, std::vector &time_secs) { auto time_stamp_beg = convert_date(chlorine_loading_ymd); - const int offset_time = compute_days(time_stamp_beg) - compute_days(model_time); - scorpio::register_file(linoz_chlorine_file,scorpio::Read); + const int offset_time = + compute_days(time_stamp_beg) - compute_days(model_time); + scorpio::register_file(linoz_chlorine_file, scorpio::Read); const int nlevs_time = scorpio::get_time_len(linoz_chlorine_file); - for (int itime = 0; itime < nlevs_time; ++itime) - { + for(int itime = 0; itime < nlevs_time; ++itime) { int date; - scorpio::read_var(linoz_chlorine_file,"date",&date,itime); - if (date >= chlorine_loading_ymd ) { + scorpio::read_var(linoz_chlorine_file, "date", &date, itime); + if(date >= chlorine_loading_ymd) { Real value; - scorpio::read_var(linoz_chlorine_file,"chlorine_loading",&value, itime); + scorpio::read_var(linoz_chlorine_file, "chlorine_loading", &value, itime); values.push_back(value); auto time_stamp = convert_date(date); - time_secs.push_back(compute_days(time_stamp)-offset_time); + time_secs.push_back(compute_days(time_stamp) - offset_time); } - }// end itime + } // end itime scorpio::release_file(linoz_chlorine_file); - } - - inline Real chlorine_loading_advance(const util::TimeStamp& ts, - std::vector& values, - std::vector& time_secs) - { +} +inline Real chlorine_loading_advance(const util::TimeStamp &ts, + std::vector &values, + std::vector &time_secs) { const int current_time = compute_days(ts); - int index=0; + int index = 0; // update index - for(int i=0; i < int(values.size()); i++){ - if (current_time > time_secs[i] ) { - index =i; - break; + for(int i = 0; i < int(values.size()); i++) { + if(current_time > time_secs[i]) { + index = i; + break; } - }// + } // - const Real delt = ( current_time - time_secs[index] ) / ( time_secs[index+1] - time_secs[index] ); - return values[index] + delt*( values[index+1] - values[index] ); - } + const Real delt = (current_time - time_secs[index]) / + (time_secs[index + 1] - time_secs[index]); + return values[index] + delt * (values[index + 1] - values[index]); +} -inline -std::shared_ptr -create_horiz_remapper ( - const std::shared_ptr& model_grid, - const std::string& trace_data_file, - const std::string& map_file, - const std::vector& var_names, - TracerFileType& tracer_file_type - ) -{ +inline std::shared_ptr create_horiz_remapper( + const std::shared_ptr &model_grid, + const std::string &trace_data_file, const std::string &map_file, + const std::vector &var_names, + TracerFileType &tracer_file_type) { using namespace ShortFieldTagsNames; - scorpio::register_file(trace_data_file,scorpio::Read); + scorpio::register_file(trace_data_file, scorpio::Read); // by default, I am assuming a zonal file. tracer_file_type = ZONAL; - int nlevs_data =-1; - if (scorpio::has_var(trace_data_file,"lev")){ - nlevs_data = scorpio::get_dimlen(trace_data_file,"lev"); + int nlevs_data = -1; + if(scorpio::has_var(trace_data_file, "lev")) { + nlevs_data = scorpio::get_dimlen(trace_data_file, "lev"); } - const bool has_altitude = scorpio::has_var(trace_data_file,"altitude"); + const bool has_altitude = scorpio::has_var(trace_data_file, "altitude"); // This type of files use altitude (zi) for vertical interpolation - if (has_altitude){ - nlevs_data = scorpio::get_dimlen(trace_data_file,"altitude"); - tracer_file_type= VERT_EMISSION; + if(has_altitude) { + nlevs_data = scorpio::get_dimlen(trace_data_file, "altitude"); + tracer_file_type = VERT_EMISSION; } - EKAT_REQUIRE_MSG (nlevs_data !=-1 , + EKAT_REQUIRE_MSG( + nlevs_data != -1, "Error: The file does not contain either lev or altitude. \n"); - const int ncols_data = scorpio::get_dimlen(trace_data_file,"ncol"); + const int ncols_data = scorpio::get_dimlen(trace_data_file, "ncol"); // This type of files use model pressure (pmid) for vertical interpolation - if (scorpio::has_var(trace_data_file,"PS")) { - tracer_file_type= FORMULA_PS; + if(scorpio::has_var(trace_data_file, "PS")) { + tracer_file_type = FORMULA_PS; } scorpio::release_file(trace_data_file); // We could use model_grid directly if using same num levels, - // but since shallow clones are cheap, we may as well do it (less lines of code) - auto horiz_interp_tgt_grid = model_grid->clone("tracer_horiz_interp_tgt_grid",true); + // but since shallow clones are cheap, we may as well do it (less lines of + // code) + auto horiz_interp_tgt_grid = + model_grid->clone("tracer_horiz_interp_tgt_grid", true); horiz_interp_tgt_grid->reset_num_vertical_lev(nlevs_data); - if ( has_altitude ) { - horiz_interp_tgt_grid->reset_field_tag_name(LEV,"altitude"); - horiz_interp_tgt_grid->reset_field_tag_name(ILEV,"altitude_int"); + if(has_altitude) { + horiz_interp_tgt_grid->reset_field_tag_name(LEV, "altitude"); + horiz_interp_tgt_grid->reset_field_tag_name(ILEV, "altitude_int"); } const int ncols_model = model_grid->get_num_global_dofs(); std::shared_ptr remapper; - if (ncols_data==ncols_model) { - remapper = std::make_shared(horiz_interp_tgt_grid,IdentityRemapper::SrcAliasTgt); + if(ncols_data == ncols_model) { + remapper = std::make_shared( + horiz_interp_tgt_grid, IdentityRemapper::SrcAliasTgt); } else { - EKAT_REQUIRE_MSG (ncols_data<=ncols_model, - "Error! We do not allow to coarsen spa data to fit the model. We only allow\n" - " spa data to be at the same or coarser resolution as the model.\n"); + EKAT_REQUIRE_MSG(ncols_data <= ncols_model, + "Error! We do not allow to coarsen spa data to fit the " + "model. We only allow\n" + " spa data to be at the same or coarser resolution " + "as the model.\n"); // We must have a valid map file - EKAT_REQUIRE_MSG (map_file!="", + EKAT_REQUIRE_MSG( + map_file != "", "ERROR: Spa data is on a different grid than the model one,\n" " but spa_remap_file is missing from SPA parameter list."); - remapper = std::make_shared(horiz_interp_tgt_grid,map_file); + remapper = + std::make_shared(horiz_interp_tgt_grid, map_file); } remapper->registration_begins(); - const auto tgt_grid = remapper->get_tgt_grid(); - const auto layout_2d = tgt_grid->get_2d_scalar_layout(); + const auto tgt_grid = remapper->get_tgt_grid(); + const auto layout_2d = tgt_grid->get_2d_scalar_layout(); const auto layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); // FieldLayout layout_3d_mid; @@ -370,123 +357,109 @@ create_horiz_remapper ( const auto nondim = ekat::units::Units::nondimensional(); - - for (auto var_name : var_names){ - Field ifield(FieldIdentifier(var_name, layout_3d_mid, nondim,tgt_grid->name())); + for(auto var_name : var_names) { + Field ifield( + FieldIdentifier(var_name, layout_3d_mid, nondim, tgt_grid->name())); ifield.allocate_view(); - remapper->register_field_from_tgt (ifield); + remapper->register_field_from_tgt(ifield); } // zonal files do not have the PS variable. - if (tracer_file_type == FORMULA_PS) - { - Field ps (FieldIdentifier("PS", layout_2d, nondim,tgt_grid->name())); + if(tracer_file_type == FORMULA_PS) { + Field ps(FieldIdentifier("PS", layout_2d, nondim, tgt_grid->name())); ps.allocate_view(); - remapper->register_field_from_tgt (ps); - + remapper->register_field_from_tgt(ps); } remapper->registration_ends(); return remapper; -} // create_horiz_remapper +} // create_horiz_remapper -inline -std::shared_ptr -create_tracer_data_reader -( - const std::shared_ptr& horiz_remapper, - const std::string& tracer_data_file) -{ +inline std::shared_ptr create_tracer_data_reader( + const std::shared_ptr &horiz_remapper, + const std::string &tracer_data_file) { std::vector io_fields; - for (int i=0; iget_num_fields(); ++i) { + for(int i = 0; i < horiz_remapper->get_num_fields(); ++i) { io_fields.push_back(horiz_remapper->get_src_field(i)); } const auto io_grid = horiz_remapper->get_src_grid(); - return std::make_shared(tracer_data_file,io_grid,io_fields,true); -} // create_tracer_data_reader - -inline -void -update_tracer_data_from_file( - std::shared_ptr& scorpio_reader, - const util::TimeStamp& ts, - const int time_index, // zero-based - AbstractRemapper& tracer_horiz_interp, - TracerData& tracer_data) -{ - // 1. read from field - scorpio_reader->read_variables(time_index); - // 2. Run the horiz remapper (it is a do-nothing op if spa data is on same grid as model) - tracer_horiz_interp.remap(/*forward = */ true); - // - const int nvars =tracer_data.nvars_; + return std::make_shared(tracer_data_file, io_grid, io_fields, + true); +} // create_tracer_data_reader + +inline void update_tracer_data_from_file( + std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, + const int time_index, // zero-based + AbstractRemapper &tracer_horiz_interp, TracerData &tracer_data) { + // 1. read from field + scorpio_reader->read_variables(time_index); + // 2. Run the horiz remapper (it is a do-nothing op if spa data is on same + // grid as model) + tracer_horiz_interp.remap(/*forward = */ true); + // + const int nvars = tracer_data.nvars_; // - for (int i = 0; i < nvars; ++i) { - tracer_data.data[i] = tracer_horiz_interp.get_tgt_field (i).get_view< Real**>(); - } - - if (tracer_data.file_type == FORMULA_PS) { - // Recall, the fields are registered in the order: tracers, ps - // 3. Copy from the tgt field of the remapper into the spa_data - tracer_data.ps = tracer_horiz_interp.get_tgt_field(nvars).get_view< Real*>(); - } - -} // update_tracer_data_from_file -inline void -update_tracer_timestate( - std::shared_ptr& scorpio_reader, - const util::TimeStamp& ts, - AbstractRemapper& tracer_horiz_interp, - TracerTimeState& time_state, - TracerData& data_tracer_beg, - TracerData& data_tracer_end) -{ + for(int i = 0; i < nvars; ++i) { + tracer_data.data[i] = + tracer_horiz_interp.get_tgt_field(i).get_view(); + } + + if(tracer_data.file_type == FORMULA_PS) { + // Recall, the fields are registered in the order: tracers, ps + // 3. Copy from the tgt field of the remapper into the spa_data + tracer_data.ps = + tracer_horiz_interp.get_tgt_field(nvars).get_view(); + } + +} // update_tracer_data_from_file +inline void update_tracer_timestate( + std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, + AbstractRemapper &tracer_horiz_interp, TracerTimeState &time_state, + TracerData &data_tracer_beg, TracerData &data_tracer_end) { // Now we check if we have to update the data that changes monthly // NOTE: This means that SPA assumes monthly data to update. Not // any other frequency. - const auto month = ts.get_month() - 1; // Make it 0-based - if (month != time_state.current_month) { + const auto month = ts.get_month() - 1; // Make it 0-based + if(month != time_state.current_month) { // const auto tracer_beg = data_tracer_beg.data; const auto tracer_end = data_tracer_end.data; - const int nvars=data_tracer_end.nvars_; + const int nvars = data_tracer_end.nvars_; // Update the SPA time state information time_state.current_month = month; - time_state.t_beg_month = util::TimeStamp({ts.get_year(),month+1,1}, {0,0,0}).frac_of_year_in_days(); - time_state.days_this_month = util::days_in_month(ts.get_year(),month+1); + time_state.t_beg_month = + util::TimeStamp({ts.get_year(), month + 1, 1}, {0, 0, 0}) + .frac_of_year_in_days(); + time_state.days_this_month = util::days_in_month(ts.get_year(), month + 1); // Copy spa_end'data into spa_beg'data, and read in the new spa_end - for (int ivar = 0; ivar < nvars ; ++ivar) - { - Kokkos::deep_copy(tracer_beg[ivar], tracer_end[ivar]); + for(int ivar = 0; ivar < nvars; ++ivar) { + Kokkos::deep_copy(tracer_beg[ivar], tracer_end[ivar]); } // Update the SPA forcing data for this month and next month // Start by copying next months data to this months data structure. - // NOTE: If the timestep is bigger than monthly this could cause the wrong values - // to be assigned. A timestep greater than a month is very unlikely so we - // will proceed. + // NOTE: If the timestep is bigger than monthly this could cause the wrong + // values + // to be assigned. A timestep greater than a month is very unlikely + // so we will proceed. int next_month = (time_state.current_month + 1) % 12; - update_tracer_data_from_file(scorpio_reader, ts, - next_month, - tracer_horiz_interp, - data_tracer_end); + update_tracer_data_from_file(scorpio_reader, ts, next_month, + tracer_horiz_interp, data_tracer_end); } -} // END updata_spa_timestate +} // END updata_spa_timestate // This function is based on the SPA::perform_time_interpolation function. - inline void perform_time_interpolation( - const TracerTimeState& time_state, - const TracerData& data_tracer_beg, - const TracerData& data_tracer_end, - const TracerData& data_tracer_out) -{ +inline void perform_time_interpolation(const TracerTimeState &time_state, + const TracerData &data_tracer_beg, + const TracerData &data_tracer_end, + const TracerData &data_tracer_out) { // NOTE: we *assume* data_beg and data_end have the *same* hybrid v coords. // IF this ever ceases to be the case, you can interp those too. // Gather time stamp info - auto& t_now = time_state.t_now; - auto& t_beg = time_state.t_beg_month; - auto& delta_t = time_state.days_this_month; + auto &t_now = time_state.t_now; + auto &t_beg = time_state.t_beg_month; + auto &delta_t = time_state.days_this_month; // We can ||ize over columns as well as over variables and bands const auto data_beg = data_tracer_beg.data; @@ -501,324 +474,289 @@ update_tracer_timestate( const int num_vars = data_tracer_end.nvars_; - const int ncol = data_tracer_beg.ncol_; + const int ncol = data_tracer_beg.ncol_; const int num_vert = data_tracer_beg.nlev_; - const int outer_iters = ncol*num_vars; + const int outer_iters = ncol * num_vars; const auto policy = ESU::get_default_team_policy(outer_iters, num_vert); - auto delta_t_fraction = (t_now-t_beg) / delta_t; + auto delta_t_fraction = (t_now - t_beg) / delta_t; - EKAT_REQUIRE_MSG (delta_t_fraction>=0 && delta_t_fraction<=1, + EKAT_REQUIRE_MSG( + delta_t_fraction >= 0 && delta_t_fraction <= 1, "Error! Convex interpolation with coefficient out of [0,1].\n" - " t_now : " + std::to_string(t_now) + "\n" - " t_beg : " + std::to_string(t_beg) + "\n" - " delta_t: " + std::to_string(delta_t) + "\n"); - - Kokkos::parallel_for("linoz_time_interp_loop", policy, - KOKKOS_LAMBDA(const Team& team) { - - // The policy is over ncols*num_vars, so retrieve icol/ivar - const int icol = team.league_rank() / num_vars; - const int ivar = team.league_rank() % num_vars; - - // Get column of beg/end/out variable - auto var_beg = ekat::subview(data_beg[ivar],icol); - auto var_end = ekat::subview(data_end[ivar],icol); - auto var_out = ekat::subview(data_out[ivar],icol); - - Kokkos::parallel_for (Kokkos::TeamVectorRange(team,num_vert), - [&] (const int& k) { - var_out(k) = linear_interp(var_beg(k),var_end(k),delta_t_fraction); - }); - // linoz files do not have ps variables. - if(ivar==1 && file_type == FORMULA_PS){ - ps_out(icol) = linear_interp(ps_beg(icol), ps_end(icol),delta_t_fraction); - } - - }); + " t_now : " + + std::to_string(t_now) + + "\n" + " t_beg : " + + std::to_string(t_beg) + + "\n" + " delta_t: " + + std::to_string(delta_t) + "\n"); + + Kokkos::parallel_for( + "linoz_time_interp_loop", policy, KOKKOS_LAMBDA(const Team &team) { + // The policy is over ncols*num_vars, so retrieve icol/ivar + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + // Get column of beg/end/out variable + auto var_beg = ekat::subview(data_beg[ivar], icol); + auto var_end = ekat::subview(data_end[ivar], icol); + auto var_out = ekat::subview(data_out[ivar], icol); + + Kokkos::parallel_for( + Kokkos::TeamVectorRange(team, num_vert), [&](const int &k) { + var_out(k) = + linear_interp(var_beg(k), var_end(k), delta_t_fraction); + }); + // linoz files do not have ps variables. + if(ivar == 1 && file_type == FORMULA_PS) { + ps_out(icol) = + linear_interp(ps_beg(icol), ps_end(icol), delta_t_fraction); + } + }); Kokkos::fence(); -} // perform_time_interpolation - -inline void -compute_source_pressure_levels( - const view_1d& ps_src, - const view_2d& p_src, - const const_view_1d& hyam, - const const_view_1d& hybm) -{ - constexpr auto P0 = C::P0; - const int ncols = ps_src.extent(0); +} // perform_time_interpolation + +inline void compute_source_pressure_levels(const view_1d &ps_src, + const view_2d &p_src, + const const_view_1d &hyam, + const const_view_1d &hybm) { + constexpr auto P0 = C::P0; + const int ncols = ps_src.extent(0); const int num_vert_packs = p_src.extent(1); const auto policy = ESU::get_default_team_policy(ncols, num_vert_packs); - Kokkos::parallel_for("tracer_compute_p_src_loop", policy, - KOKKOS_LAMBDA (const Team& team) { - const int icol = team.league_rank(); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,num_vert_packs), - [&](const int k) { - p_src(icol,k) = ps_src(icol) * hybm(k) + P0 * hyam(k); - }); - }); -} // compute_source_pressure_levels - + Kokkos::parallel_for( + "tracer_compute_p_src_loop", policy, KOKKOS_LAMBDA(const Team &team) { + const int icol = team.league_rank(); + Kokkos::parallel_for( + Kokkos::TeamVectorRange(team, num_vert_packs), [&](const int k) { + p_src(icol, k) = ps_src(icol) * hybm(k) + P0 * hyam(k); + }); + }); +} // compute_source_pressure_levels // Linoz NetCDF files use levs instead of formula_terms. // This function allocates a view, so we need to do it during initialization. // Thus, we assume that source pressure is independent of time, // which is the case for Linoz files (zonal file). - inline void compute_p_src_zonal_files(const std::string& tracer_file_name, - const view_2d& p_src) - { - EKAT_REQUIRE_MSG (p_src.data()!=0 , - "Error: p_src has not been allocated. \n"); - // Read in levs in start/end data - // FIXME: units are mbar; how can I get units using scorpio interface - auto nondim = ekat::units::Units::nondimensional(); - scorpio::register_file(tracer_file_name,scorpio::Read); - const int nlevs_data = scorpio::get_dimlen(tracer_file_name,"lev"); - view_1d_host levs_h("levs_h", nlevs_data); - scorpio::read_var(tracer_file_name, "lev", levs_h.data()); - scorpio::release_file(tracer_file_name); - view_1d levs("levs", nlevs_data); - Kokkos::deep_copy(levs, levs_h); - - const int ncol = p_src.extent(0); - EKAT_REQUIRE_MSG (p_src.extent(1) == nlevs_data , +inline void compute_p_src_zonal_files(const std::string &tracer_file_name, + const view_2d &p_src) { + EKAT_REQUIRE_MSG(p_src.data() != 0, + "Error: p_src has not been allocated. \n"); + // Read in levs in start/end data + // FIXME: units are mbar; how can I get units using scorpio interface + auto nondim = ekat::units::Units::nondimensional(); + scorpio::register_file(tracer_file_name, scorpio::Read); + const int nlevs_data = scorpio::get_dimlen(tracer_file_name, "lev"); + view_1d_host levs_h("levs_h", nlevs_data); + scorpio::read_var(tracer_file_name, "lev", levs_h.data()); + scorpio::release_file(tracer_file_name); + view_1d levs("levs", nlevs_data); + Kokkos::deep_copy(levs, levs_h); + + const int ncol = p_src.extent(0); + EKAT_REQUIRE_MSG( + p_src.extent(1) == nlevs_data, "Error: p_src has a different number of levels than the source data. \n"); - const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); - const int pi =haero::Constants::pi; - Kokkos::parallel_for("pressure_computation", policy_pressure, - KOKKOS_LAMBDA(const Team& team) { + const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); + const int pi = haero::Constants::pi; + Kokkos::parallel_for( + "pressure_computation", policy_pressure, KOKKOS_LAMBDA(const Team &team) { const int icol = team.league_rank(); Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), - [&] (const Int& kk) { - // mbar->pascals - // FIXME: Does EAMxx have a better method to convert units?" - p_src(icol, kk) = levs(kk)*100; + [&](const Int &kk) { + // mbar->pascals + // FIXME: Does EAMxx have a better method to + // convert units?" + p_src(icol, kk) = levs(kk) * 100; + }); }); - }); Kokkos::fence(); - } - -inline void -perform_vertical_interpolation( - const view_2d& p_src_c, - const const_view_2d& p_tgt_c, - const TracerData& input, - const view_2d output[]) -{ +} + +inline void perform_vertical_interpolation(const view_2d &p_src_c, + const const_view_2d &p_tgt_c, + const TracerData &input, + const view_2d output[]) { // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_==output[0].extent(0)); + EKAT_REQUIRE(input.ncol_ == output[0].extent(0)); #if 1 // FIXME: I was encountering a compilation error when using const_view_2d. // The issue is fixed by https://github.com/E3SM-Project/EKAT/pull/346. - // I will keep this code until this PR is merged into the EKAT master branch and - // we update the EKAT version in our code. - // I am converting const_view_2d to view_2d. + // I will keep this code until this PR is merged into the EKAT master branch + // and we update the EKAT version in our code. I am converting const_view_2d + // to view_2d. auto p_src_ptr = (Real *)p_src_c.data(); - view_2d p_src(p_src_ptr,p_src_c.extent(0),p_src_c.extent(1)); + view_2d p_src(p_src_ptr, p_src_c.extent(0), p_src_c.extent(1)); auto p_tgt_ptr = (Real *)p_tgt_c.data(); - view_2d p_tgt(p_tgt_ptr,p_tgt_c.extent(0),p_tgt_c.extent(1)); + view_2d p_tgt(p_tgt_ptr, p_tgt_c.extent(0), p_tgt_c.extent(1)); #else const auto p_src = p_src_c; const auto p_tgt = p_tgt_c; #endif - const int ncols = input.ncol_; + const int ncols = input.ncol_; // FIXME: I am getting FPEs if I do not subtract 1 from nlevs_src. - const int nlevs_src = input.nlev_-1; + const int nlevs_src = input.nlev_ - 1; const int nlevs_tgt = output[0].extent(1); - LIV vert_interp(ncols,nlevs_src,nlevs_tgt); + LIV vert_interp(ncols, nlevs_src, nlevs_tgt); // We can ||ize over columns as well as over variables and bands - const int num_vars = input.nvars_; + const int num_vars = input.nvars_; const int num_vert_packs = nlevs_tgt; const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); // Setup the linear interpolation object - Kokkos::parallel_for("tracer_vert_interp_setup_loop", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - - const int icol = team.league_rank(); - // Setup - vert_interp.setup(team, ekat::subview(p_src,icol), - ekat::subview(p_tgt,icol)); - }); + Kokkos::parallel_for( + "tracer_vert_interp_setup_loop", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const &team) { + const int icol = team.league_rank(); + // Setup + vert_interp.setup(team, ekat::subview(p_src, icol), + ekat::subview(p_tgt, icol)); + }); Kokkos::fence(); // Now use the interpolation object in || over all variables. - const int outer_iters = ncols*num_vars; - const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); - Kokkos::parallel_for("tracer_vert_interp_loop", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const& team) { - - const int icol = team.league_rank() / num_vars; - const int ivar = team.league_rank() % num_vars; - - const auto x1 = ekat::subview(p_src,icol); - const auto x2 = ekat::subview(p_tgt,icol); - - const auto y1 = ekat::subview(input.data[ivar],icol); - const auto y2 = ekat::subview(output[ivar],icol); - - vert_interp.lin_interp(team, x1, x2, y1, y2, icol); - }); + const int outer_iters = ncols * num_vars; + const auto policy_interp = + ESU::get_default_team_policy(outer_iters, num_vert_packs); + Kokkos::parallel_for( + "tracer_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(typename LIV::MemberType const &team) { + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + const auto x1 = ekat::subview(p_src, icol); + const auto x2 = ekat::subview(p_tgt, icol); + + const auto y1 = ekat::subview(input.data[ivar], icol); + const auto y2 = ekat::subview(output[ivar], icol); + + vert_interp.lin_interp(team, x1, x2, y1, y2, icol); + }); Kokkos::fence(); } -// rebin is a port from: https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 -inline void rebin(int nsrc, int ntrg, - const const_view_1d& src_x, - const Real trg_x[], - const view_1d& src, - const view_1d& trg) { - - for (int i = 0; i < ntrg; ++i) { - Real tl = trg_x[i]; - if (tl < src_x(nsrc)) { - int sil = 0; - for (; sil <= nsrc; ++sil) { - if (tl <= src_x(sil)) { - break; - } - } - Real tu = trg_x[i + 1]; - int siu = 0; - for (; siu <= nsrc; ++siu) { - if (tu <= src_x(siu)) { - break; - } - } - Real y = 0.0; - sil = haero::max(sil, 1); - siu = haero::min(siu, nsrc); - for (int si = sil; si <= siu; ++si) { - int si1 = si - 1; - Real sl = haero::max(tl, src_x(si1)); - Real su = haero::min(tu, src_x(si)); - y += (su - sl) * src(si1); - } - trg(i) = y / (trg_x[i + 1] - trg_x[i]); - } else { - trg(i) = 0.0; +// rebin is a port from: +// https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 +inline void rebin(int nsrc, int ntrg, const const_view_1d &src_x, + const Real trg_x[], const view_1d &src, const view_1d &trg) { + for(int i = 0; i < ntrg; ++i) { + Real tl = trg_x[i]; + if(tl < src_x(nsrc)) { + int sil = 0; + for(; sil <= nsrc; ++sil) { + if(tl <= src_x(sil)) { + break; } + } + Real tu = trg_x[i + 1]; + int siu = 0; + for(; siu <= nsrc; ++siu) { + if(tu <= src_x(siu)) { + break; + } + } + Real y = 0.0; + sil = haero::max(sil, 1); + siu = haero::min(siu, nsrc); + for(int si = sil; si <= siu; ++si) { + int si1 = si - 1; + Real sl = haero::max(tl, src_x(si1)); + Real su = haero::min(tu, src_x(si)); + y += (su - sl) * src(si1); + } + trg(i) = y / (trg_x[i + 1] - trg_x[i]); + } else { + trg(i) = 0.0; } -}// rebin - -inline void perform_vertical_interpolation(const const_view_1d& altitude_int, - const const_view_2d& zi, - const TracerData& input, - const view_2d output[]) -{ - EKAT_REQUIRE_MSG (input.file_type == VERT_EMISSION, + } +} // rebin + +inline void perform_vertical_interpolation(const const_view_1d &altitude_int, + const const_view_2d &zi, + const TracerData &input, + const view_2d output[]) { + EKAT_REQUIRE_MSG( + input.file_type == VERT_EMISSION, "Error! vertical interpolation only with altitude variable. \n"); - const int ncols = input.ncol_; - const int num_vars = input.nvars_; - const int ntrg = output[0].extent(1); + const int ncols = input.ncol_; + const int num_vars = input.nvars_; + const int ntrg = output[0].extent(1); const int num_vert_packs = ntrg; - const int outer_iters = ncols*num_vars; - const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); + const int outer_iters = ncols * num_vars; + const auto policy_interp = + ESU::get_default_team_policy(outer_iters, num_vert_packs); // FIXME: Get m2km from emaxx. - const Real m2km =1e-3; - const auto& src_x = altitude_int; - const int nsrc = input.nlev_; + const Real m2km = 1e-3; + const auto &src_x = altitude_int; + const int nsrc = input.nlev_; constexpr int pver = mam4::nlev; - const int pverp = pver + 1; - - Kokkos::parallel_for("tracer_vert_interp_loop", policy_interp, - KOKKOS_LAMBDA(const Team& team) { - - const int icol = team.league_rank() / num_vars; - const int ivar = team.league_rank() % num_vars; - - const auto src = ekat::subview(input.data[ivar],icol); - const auto trg = ekat::subview(output[ivar],icol); + const int pverp = pver + 1; + + Kokkos::parallel_for( + "tracer_vert_interp_loop", policy_interp, + KOKKOS_LAMBDA(const Team &team) { + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + + const auto src = ekat::subview(input.data[ivar], icol); + const auto trg = ekat::subview(output[ivar], icol); + + // trg_x + Real trg_x[pver + 1]; + // I am trying to do this: + // model_z(1:pverp) = m2km * state(c)%zi(i,pverp:1:-1) + for(int i = 0; i < pverp; ++i) { + trg_x[pverp - i - 1] = m2km * zi(icol, i); + } + team.team_barrier(); - // trg_x - Real trg_x[pver+1]; - // I am trying to do this: - // model_z(1:pverp) = m2km * state(c)%zi(i,pverp:1:-1) - for (int i = 0; i < pverp; ++i) - { - trg_x[pverp-i-1] = m2km * zi(icol,i); - } - team.team_barrier(); - - rebin(nsrc, ntrg, - src_x, - trg_x, - src, - trg); - }); + rebin(nsrc, ntrg, src_x, trg_x, src, trg); + }); } -inline void -advance_tracer_data(std::shared_ptr& scorpio_reader, - AbstractRemapper& tracer_horiz_interp, - const util::TimeStamp& ts, - TracerTimeState& time_state, - TracerData& data_tracer_beg, - TracerData& data_tracer_end, - TracerData& data_tracer_out, - const view_2d& p_src, - const const_view_2d& p_tgt, - const const_view_1d& zi_src, - const const_view_2d& zi_tgt, - const view_2d output[]) -{ - - /* Update the TracerTimeState to reflect the current time, note the addition of dt */ +inline void advance_tracer_data( + std::shared_ptr &scorpio_reader, + AbstractRemapper &tracer_horiz_interp, const util::TimeStamp &ts, + TracerTimeState &time_state, TracerData &data_tracer_beg, + TracerData &data_tracer_end, TracerData &data_tracer_out, + const view_2d &p_src, const const_view_2d &p_tgt, + const const_view_1d &zi_src, const const_view_2d &zi_tgt, + const view_2d output[]) { + /* Update the TracerTimeState to reflect the current time, note the addition + * of dt */ time_state.t_now = ts.frac_of_year_in_days(); /* Update time state and if the month has changed, update the data.*/ - update_tracer_timestate( - scorpio_reader, - ts, - tracer_horiz_interp, - time_state, - data_tracer_beg, - data_tracer_end); + update_tracer_timestate(scorpio_reader, ts, tracer_horiz_interp, time_state, + data_tracer_beg, data_tracer_end); // Step 1. Perform time interpolation - perform_time_interpolation( - time_state, - data_tracer_beg, - data_tracer_end, - data_tracer_out); - - if (data_tracer_out.file_type == FORMULA_PS ) - { - // Step 2. Compute source pressure levels - compute_source_pressure_levels( - data_tracer_out.ps, - p_src, - data_tracer_out.hyam, - data_tracer_out.hybm); + perform_time_interpolation(time_state, data_tracer_beg, data_tracer_end, + data_tracer_out); + + if(data_tracer_out.file_type == FORMULA_PS) { + // Step 2. Compute source pressure levels + compute_source_pressure_levels(data_tracer_out.ps, p_src, + data_tracer_out.hyam, data_tracer_out.hybm); } // Step 3. Perform vertical interpolation - if (data_tracer_out.file_type == FORMULA_PS || data_tracer_out.file_type == ZONAL) - { - perform_vertical_interpolation( - p_src, - p_tgt, - data_tracer_out, - output); - } else if (data_tracer_out.file_type == VERT_EMISSION) { - perform_vertical_interpolation( - zi_src, - zi_tgt, - data_tracer_out, - output); - + if(data_tracer_out.file_type == FORMULA_PS || + data_tracer_out.file_type == ZONAL) { + perform_vertical_interpolation(p_src, p_tgt, data_tracer_out, output); + } else if(data_tracer_out.file_type == VERT_EMISSION) { + perform_vertical_interpolation(zi_src, zi_tgt, data_tracer_out, output); } +} // advance_tracer_data -}// advance_tracer_data - - -} // namespace scream::mam_coupling -#endif //EAMXX_MAM_HELPER_MICRO +} // namespace scream::mam_coupling +#endif // EAMXX_MAM_HELPER_MICRO diff --git a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp b/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp index 71cee1eeb40..39a886744f4 100644 --- a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp +++ b/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #include #include @@ -25,10 +25,10 @@ constexpr int nqtendbb() { return 4; } // MAM4 aerosol microphysics configuration data struct AmicPhysConfig { // these switches activate various aerosol microphysics processes - bool do_cond; // condensation (a.k.a gas-aerosol exchange) - bool do_rename; // mode "renaming" - bool do_newnuc; // gas -> aerosol nucleation - bool do_coag; // aerosol coagulation + bool do_cond; // condensation (a.k.a gas-aerosol exchange) + bool do_rename; // mode "renaming" + bool do_newnuc; // gas -> aerosol nucleation + bool do_coag; // aerosol coagulation // configurations for specific aerosol microphysics mam4::GasAerExchProcess::ProcessConfig condensation; @@ -62,7 +62,8 @@ KOKKOS_INLINE_FUNCTION Real fcvt_gas(int gas_id) { return fcvt_gas_[gas_id]; } KOKKOS_INLINE_FUNCTION Real fcvt_aer(int aero_id) { - static const Real fcvt_aer_[AeroConfig::num_aerosol_ids()] = {1, 1, 1, 1, 1, 1, 1}; + static const Real fcvt_aer_[AeroConfig::num_aerosol_ids()] = {1, 1, 1, 1, + 1, 1, 1}; return fcvt_aer_[aero_id]; } @@ -77,14 +78,14 @@ KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_aer() { return 2; } KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_num() { return 3; } KOKKOS_INLINE_FUNCTION int lmapcc_all(int index) { static const int lmapcc_all_[gas_pcnst()] = { - lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_nul(), lmapcc_val_nul(), - lmapcc_val_gas(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num()}; + lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_nul(), lmapcc_val_nul(), + lmapcc_val_gas(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_num()}; return lmapcc_all_[index]; } @@ -101,22 +102,25 @@ KOKKOS_INLINE_FUNCTION int lmap_gas(int mode) { } // Where lmapcc_val_aer are defined in lmapcc_all KOKKOS_INLINE_FUNCTION int lmassptr_amode(int aero_id, int mode) { - static const int lmassptr_amode_[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()] = { - {5, 13, 18, 26}, {6, 14, 19, 27}, {7, 15, 20, 28}, {8, 16, 21, -6}, - {9, -6, 22, -6}, {10, -6, 23, -6}, {11, -6, 24, -6}}; + static const int lmassptr_amode_[AeroConfig::num_aerosol_ids()] + [AeroConfig::num_modes()] = { + {5, 13, 18, 26}, {6, 14, 19, 27}, + {7, 15, 20, 28}, {8, 16, 21, -6}, + {9, -6, 22, -6}, {10, -6, 23, -6}, + {11, -6, 24, -6}}; return lmassptr_amode_[aero_id][mode]; } KOKKOS_INLINE_FUNCTION void subarea_partition_factors( const Real - q_int_cell_avg, // in grid cell mean interstitial aerosol mixing ratio + q_int_cell_avg, // in grid cell mean interstitial aerosol mixing ratio const Real - q_cbn_cell_avg, // in grid cell mean cloud-borne aerosol mixing ratio - const Real fcldy, // in cloudy fraction of the grid cell - const Real fclea, // in clear fraction of the grid cell - Real &part_fac_q_int_clea, // out - Real &part_fac_q_int_cldy) // out + q_cbn_cell_avg, // in grid cell mean cloud-borne aerosol mixing ratio + const Real fcldy, // in cloudy fraction of the grid cell + const Real fclea, // in clear fraction of the grid cell + Real &part_fac_q_int_clea, // out + Real &part_fac_q_int_cldy) // out { // Calculate mixing ratios of each subarea @@ -151,36 +155,37 @@ void subarea_partition_factors( KOKKOS_INLINE_FUNCTION void construct_subareas_1gridcell( - const Real cld, // in - const Real relhumgcm, // in - const Real q_pregaschem[gas_pcnst()], // in q TMRs before - // gas-phase chemistry - const Real q_precldchem[gas_pcnst()], // in q TMRs before - // cloud chemistry - const Real qqcw_precldchem[gas_pcnst()], // in qqcw TMRs before - // cloud chemistry - const Real q[gas_pcnst()], // in current tracer mixing ratios (TMRs) - // *** MUST BE #/kmol-air for number - // *** MUST BE mol/mol-air for mass - const Real qqcw[gas_pcnst()], // in like q but for - // cloud-borner tracers - int &nsubarea, // out - int &ncldy_subarea, // out - int &jclea, // out - int &jcldy, // out - bool iscldy_subarea[maxsubarea()], // out - Real afracsub[maxsubarea()], // out - Real relhumsub[maxsubarea()], // out - Real qsub1[gas_pcnst()][maxsubarea()], // out interstitial - Real qsub2[gas_pcnst()][maxsubarea()], // out interstitial - Real qsub3[gas_pcnst()][maxsubarea()], // out interstitial - Real qqcwsub1[gas_pcnst()][maxsubarea()], // out cloud-borne - Real qqcwsub2[gas_pcnst()][maxsubarea()], // out cloud-borne - Real qqcwsub3[gas_pcnst()][maxsubarea()], // outcloud-borne - Real qaerwatsub3[AeroConfig::num_modes()] - [maxsubarea()], // out aerosol water mixing ratios (mol/mol) - Real qaerwat[AeroConfig::num_modes()] // in aerosol water mixing ratio - // (kg/kg, NOT mol/mol) + const Real cld, // in + const Real relhumgcm, // in + const Real q_pregaschem[gas_pcnst()], // in q TMRs before + // gas-phase chemistry + const Real q_precldchem[gas_pcnst()], // in q TMRs before + // cloud chemistry + const Real qqcw_precldchem[gas_pcnst()], // in qqcw TMRs before + // cloud chemistry + const Real q[gas_pcnst()], // in current tracer mixing ratios (TMRs) + // *** MUST BE #/kmol-air for number + // *** MUST BE mol/mol-air for mass + const Real qqcw[gas_pcnst()], // in like q but for + // cloud-borner tracers + int &nsubarea, // out + int &ncldy_subarea, // out + int &jclea, // out + int &jcldy, // out + bool iscldy_subarea[maxsubarea()], // out + Real afracsub[maxsubarea()], // out + Real relhumsub[maxsubarea()], // out + Real qsub1[gas_pcnst()][maxsubarea()], // out interstitial + Real qsub2[gas_pcnst()][maxsubarea()], // out interstitial + Real qsub3[gas_pcnst()][maxsubarea()], // out interstitial + Real qqcwsub1[gas_pcnst()][maxsubarea()], // out cloud-borne + Real qqcwsub2[gas_pcnst()][maxsubarea()], // out cloud-borne + Real qqcwsub3[gas_pcnst()][maxsubarea()], // outcloud-borne + Real + qaerwatsub3[AeroConfig::num_modes()] + [maxsubarea()], // out aerosol water mixing ratios (mol/mol) + Real qaerwat[AeroConfig::num_modes()] // in aerosol water mixing ratio + // (kg/kg, NOT mol/mol) ) { static constexpr int num_modes = AeroConfig::num_modes(); // cloud chemistry is only on when cld(i,k) >= 1.0e-5_wp @@ -206,53 +211,46 @@ void construct_subareas_1gridcell( // sub-area (nsubarea = 1) otherwise, the grid-cell has a // clear and a cloudy sub-area (nsubarea = 2) - Real zfcldy = 0; - nsubarea = 0; + Real zfcldy = 0; + nsubarea = 0; ncldy_subarea = 0; - jclea = 0; - jcldy = 0; + jclea = 0; + jcldy = 0; - if (cld < fcld_locutoff) { - nsubarea = 1; - jclea = 1; - } else if (cld > fcld_hicutoff) { - zfcldy = 1.0; + if(cld < fcld_locutoff) { nsubarea = 1; + jclea = 1; + } else if(cld > fcld_hicutoff) { + zfcldy = 1.0; + nsubarea = 1; ncldy_subarea = 1; - jcldy = 1; + jcldy = 1; } else { - zfcldy = cld; - nsubarea = 2; + zfcldy = cld; + nsubarea = 2; ncldy_subarea = 1; - jclea = 1; - jcldy = 2; + jclea = 1; + jcldy = 2; } const Real zfclea = 1.0 - zfcldy; - for (int i = 0; i < maxsubarea(); ++i) - iscldy_subarea[i] = false; - if (jcldy > 0) - iscldy_subarea[jcldy - 1] = true; - for (int i = 0; i < maxsubarea(); ++i) - afracsub[i] = 0.0; - if (jclea > 0) - afracsub[jclea - 1] = zfclea; - if (jcldy > 0) - afracsub[jcldy - 1] = zfcldy; + for(int i = 0; i < maxsubarea(); ++i) iscldy_subarea[i] = false; + if(jcldy > 0) iscldy_subarea[jcldy - 1] = true; + for(int i = 0; i < maxsubarea(); ++i) afracsub[i] = 0.0; + if(jclea > 0) afracsub[jclea - 1] = zfclea; + if(jcldy > 0) afracsub[jcldy - 1] = zfcldy; // cldy_rh_sameas_clear is just to match mam_refactor. Compiler should // optimize away. const int cldy_rh_sameas_clear = 0; - if (ncldy_subarea <= 0) { - for (int i = 0; i < maxsubarea(); ++i) - relhumsub[i] = relhumgcm; - } else if (cldy_rh_sameas_clear > 0) { - for (int i = 0; i < maxsubarea(); ++i) - relhumsub[i] = relhumgcm; + if(ncldy_subarea <= 0) { + for(int i = 0; i < maxsubarea(); ++i) relhumsub[i] = relhumgcm; + } else if(cldy_rh_sameas_clear > 0) { + for(int i = 0; i < maxsubarea(); ++i) relhumsub[i] = relhumgcm; } else { - if (jcldy > 0) { + if(jcldy > 0) { relhumsub[jcldy - 1] = 1.0; - if (jclea > 0) { + if(jclea > 0) { const Real tmpa = (relhumgcm - afracsub[jcldy - 1]) / afracsub[jclea - 1]; relhumsub[jclea - 1] = haero::max(0.0, haero::min(1.0, tmpa)); @@ -268,7 +266,7 @@ void construct_subareas_1gridcell( // ---------------------------------------------------------------------------- // Interstitial aerosols Real qgcm1[gas_pcnst()], qgcm2[gas_pcnst()], qgcm3[gas_pcnst()]; - for (int i = 0; i < gas_pcnst(); ++i) { + for(int i = 0; i < gas_pcnst(); ++i) { qgcm1[i] = haero::max(0.0, q_pregaschem[i]); qgcm2[i] = haero::max(0.0, q_precldchem[i]); qgcm3[i] = haero::max(0.0, q[i]); @@ -276,14 +274,14 @@ void construct_subareas_1gridcell( // Cloud-borne aerosols Real qqcwgcm2[gas_pcnst()], qqcwgcm3[gas_pcnst()]; - for (int i = 0; i < gas_pcnst(); ++i) { + for(int i = 0; i < gas_pcnst(); ++i) { qqcwgcm2[i] = haero::max(0.0, qqcw_precldchem[i]); qqcwgcm3[i] = haero::max(0.0, qqcw[i]); } // aerosol water Real qaerwatgcm3[num_modes] = {}; - for (int i = 0; i < num_modes; ++i) { + for(int i = 0; i < num_modes; ++i) { qaerwatgcm3[i] = haero::max(0.0, qaerwat[i]); } @@ -292,16 +290,16 @@ void construct_subareas_1gridcell( // ---------------------------------------------------------------------------- { const int n = haero::min(maxsubarea(), nsubarea + 1); - for (int i = 0; i < n; ++i) { - for (int j = 0; j < gas_pcnst(); ++j) { - qsub1[j][i] = 0.0; - qsub2[j][i] = 0.0; - qsub3[j][i] = 0.0; + for(int i = 0; i < n; ++i) { + for(int j = 0; j < gas_pcnst(); ++j) { + qsub1[j][i] = 0.0; + qsub2[j][i] = 0.0; + qsub3[j][i] = 0.0; qqcwsub1[j][i] = 0.0; qqcwsub2[j][i] = 0.0; qqcwsub3[j][i] = 0.0; } - for (int j = 0; j < num_modes; ++j) { + for(int j = 0; j < num_modes; ++j) { qaerwatsub3[j][i] = 0.0; } } @@ -320,17 +318,15 @@ void construct_subareas_1gridcell( // ************************************************************************************************* // Category I: partly cloudy case // ************************************************************************************************* - if ((jclea > 0) && (jcldy > 0) && (jclea + jcldy == 3) && (nsubarea == 2)) { - + if((jclea > 0) && (jcldy > 0) && (jclea + jcldy == 3) && (nsubarea == 2)) { // --------------------------------------------------------------------- // Set GAS mixing ratios in sub-areas (for the condensing gases only!!) // --------------------------------------------------------------------- - for (int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if (lmapcc_all(lmz) == lmapcc_val_gas()) { - + for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { + if(lmapcc_all(lmz) == lmapcc_val_gas()) { // assume gas in both sub-areas before gas-chem and cloud-chem equal // grid-cell mean - for (int i = 0; i < nsubarea; ++i) { + for(int i = 0; i < nsubarea; ++i) { qsub1[lmz][i] = qgcm1[lmz]; qsub2[lmz][i] = qgcm2[lmz]; } @@ -343,7 +339,7 @@ void construct_subareas_1gridcell( (qgcm3[lmz] - zfclea * qsub3[lmz][jclea - 1]) / zfcldy; // check that this does not produce a negative value - if (qsub3[lmz][jcldy - 1] < 0.0) { + if(qsub3[lmz][jcldy - 1] < 0.0) { qsub3[lmz][jcldy - 1] = 0.0; qsub3[lmz][jclea - 1] = qgcm3[lmz] / zfclea; } @@ -355,11 +351,11 @@ void construct_subareas_1gridcell( // are applied to all mass and number mixing ratios in all modes. // --------------------------------------------------------------------- // loop thru log-normal modes - for (int n = 0; n < num_modes; ++n) { + for(int n = 0; n < num_modes; ++n) { // number - then mass of individual species - of a mode - for (int l2 = -1; l2 < num_species_mode(n); ++l2) { + for(int l2 = -1; l2 < num_species_mode(n); ++l2) { int lc; - if (l2 == -1) + if(l2 == -1) lc = numptr_amode(n); else lc = lmassptr_amode(l2, n); @@ -373,7 +369,7 @@ void construct_subareas_1gridcell( // --------------------------------------------------------------------- // Set INTERSTITIAL AEROSOL mixing ratios in sub-areas. // --------------------------------------------------------------------- - for (int n = 0; n < num_modes; ++n) { + for(int n = 0; n < num_modes; ++n) { // ------------------------------------- // Aerosol number // ------------------------------------- @@ -406,10 +402,10 @@ void construct_subareas_1gridcell( // Compute the total mixing ratios by summing up the individual species - tmp_q_cellavg_int = 0.0; // grid cell mean, interstitial - tmp_q_cellavg_cbn = 0.0; // grid cell mean, cloud-borne + tmp_q_cellavg_int = 0.0; // grid cell mean, interstitial + tmp_q_cellavg_cbn = 0.0; // grid cell mean, cloud-borne - for (int l2 = 0; l2 < num_species_mode(n); ++l2) { + for(int l2 = 0; l2 < num_species_mode(n); ++l2) { tmp_q_cellavg_int += qgcm2[lmassptr_amode(l2, n)]; tmp_q_cellavg_cbn += qqcwgcm2[lmassptr_amode(l2, n)]; } @@ -422,7 +418,7 @@ void construct_subareas_1gridcell( // Apply the partitioning factors to calculate sub-area mean mass mixing // ratios - for (int l2 = 0; l2 < num_species_mode(n); ++l2) { + for(int l2 = 0; l2 < num_species_mode(n); ++l2) { const int la = lmassptr_amode(l2, n); qsub2[la][jclea - 1] = qgcm2[la] * mass_part_fac_clea; @@ -436,7 +432,7 @@ void construct_subareas_1gridcell( // Category II: all clear, or cld < 1e-5 // In this case, zfclea=1 and zfcldy=0 // ************************************************************************************************* - } else if ((jclea == 1) && (jcldy == 0) && (nsubarea == 1)) { + } else if((jclea == 1) && (jcldy == 0) && (nsubarea == 1)) { // // put all the gases and interstitial aerosols in the clear sub-area // and set mix-ratios = 0 in cloudy sub-area @@ -445,11 +441,11 @@ void construct_subareas_1gridcell( // unchanged (i.e., this routine only changes qqcw when cld >= 1e-5) // - for (int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if (0 < lmapcc_all(lmz)) { - qsub1[lmz][jclea - 1] = qgcm1[lmz]; - qsub2[lmz][jclea - 1] = qgcm2[lmz]; - qsub3[lmz][jclea - 1] = qgcm3[lmz]; + for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { + if(0 < lmapcc_all(lmz)) { + qsub1[lmz][jclea - 1] = qgcm1[lmz]; + qsub2[lmz][jclea - 1] = qgcm2[lmz]; + qsub3[lmz][jclea - 1] = qgcm3[lmz]; qqcwsub2[lmz][jclea - 1] = qqcwgcm2[lmz]; qqcwsub3[lmz][jclea - 1] = qqcwgcm3[lmz]; } @@ -458,23 +454,24 @@ void construct_subareas_1gridcell( // Category III: all cloudy, or cld > 0.999 // in this case, zfcldy= and zfclea=0 // ************************************************************************************************* - } else if ((jclea == 0) && (jcldy == 1) && (nsubarea == 1)) { + } else if((jclea == 0) && (jcldy == 1) && (nsubarea == 1)) { // // put all the gases and interstitial aerosols in the cloudy sub-area // and set mix-ratios = 0 in clear sub-area // - for (int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if (0 < lmapcc_all(lmz)) { - qsub1[lmz][jcldy - 1] = qgcm1[lmz]; - qsub2[lmz][jcldy - 1] = qgcm2[lmz]; - qsub3[lmz][jcldy - 1] = qgcm3[lmz]; + for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { + if(0 < lmapcc_all(lmz)) { + qsub1[lmz][jcldy - 1] = qgcm1[lmz]; + qsub2[lmz][jcldy - 1] = qgcm2[lmz]; + qsub3[lmz][jcldy - 1] = qgcm3[lmz]; qqcwsub2[lmz][jcldy - 1] = qqcwgcm2[lmz]; qqcwsub3[lmz][jcldy - 1] = qqcwgcm3[lmz]; } } // ************************************************************************************************* - } else { // this should not happen - EKAT_KERNEL_REQUIRE_MSG(false, "*** modal_aero_amicphys - bad jclea, jcldy, nsubarea!"); + } else { // this should not happen + EKAT_KERNEL_REQUIRE_MSG( + false, "*** modal_aero_amicphys - bad jclea, jcldy, nsubarea!"); } // ************************************************************************************************* @@ -482,19 +479,18 @@ void construct_subareas_1gridcell( // aerosol water -- how to treat this in sub-areas needs more work/thinking // currently modal_aero_water_uptake calculates qaerwat using // the grid-cell mean interstital-aerosol mix-rats and the clear-area rh - for (int jsub = 0; jsub < nsubarea; ++jsub) - for (int i = 0; i < num_modes; ++i) - qaerwatsub3[i][jsub] = qaerwatgcm3[i]; + for(int jsub = 0; jsub < nsubarea; ++jsub) + for(int i = 0; i < num_modes; ++i) qaerwatsub3[i][jsub] = qaerwatgcm3[i]; // ------------------------------------------------------------------------------------ - if (nsubarea == 1) { + if(nsubarea == 1) { // the j=1 subarea is used for some diagnostics // but is not used in actual calculations const int j = 1; - for (int i = 0; i < gas_pcnst(); ++i) { - qsub1[i][j] = 0.0; - qsub2[i][j] = 0.0; - qsub3[i][j] = 0.0; + for(int i = 0; i < gas_pcnst(); ++i) { + qsub1[i][j] = 0.0; + qsub2[i][j] = 0.0; + qsub3[i][j] = 0.0; qqcwsub2[i][j] = 0.0; qqcwsub3[i][j] = 0.0; } @@ -503,11 +499,11 @@ void construct_subareas_1gridcell( KOKKOS_INLINE_FUNCTION void mam_amicphys_1subarea_clear( - const AmicPhysConfig& config, const int nstep, const Real deltat, const int jsub, - const int nsubarea, const bool iscldy_subarea, const Real afracsub, - const Real temp, const Real pmid, const Real pdel, const Real zmid, - const Real pblh, const Real relhum, Real dgn_a[AeroConfig::num_modes()], - Real dgn_awet[AeroConfig::num_modes()], + const AmicPhysConfig &config, const int nstep, const Real deltat, + const int jsub, const int nsubarea, const bool iscldy_subarea, + const Real afracsub, const Real temp, const Real pmid, const Real pdel, + const Real zmid, const Real pblh, const Real relhum, + Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], Real wetdens[AeroConfig::num_modes()], const Real qgas1[AeroConfig::num_gas_ids()], const Real qgas3[AeroConfig::num_gas_ids()], @@ -522,13 +518,13 @@ void mam_amicphys_1subarea_clear( [nqtendaa()], const Real qwtr3[AeroConfig::num_modes()], Real qwtr4[AeroConfig::num_modes()]) { - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); + static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); + static constexpr int num_modes = AeroConfig::num_modes(); static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); // Turn off nh3 for now. This is a future enhancement. - static constexpr int igas_nh3 = -999888777; // Same as mam_refactor + static constexpr int igas_nh3 = -999888777; // Same as mam_refactor static constexpr int iaer_so4 = static_cast(AeroId::SO4); static constexpr int iaer_pom = static_cast(AeroId::POM); @@ -544,8 +540,8 @@ void mam_amicphys_1subarea_clear( // air molar density (kmol/m3) // const Real r_universal = Constants::r_gas; // [mJ/(K mol)] - const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor - const Real aircon = pmid / (1000 * r_universal * temp); + const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor + const Real aircon = pmid / (1000 * r_universal * temp); const Real alnsg_aer[num_modes] = {0.58778666490211906, 0.47000362924573563, 0.58778666490211906, 0.47000362924573563}; const Real uptk_rate_factor[num_gas_ids] = {0.81, 1.0, 1.0}; @@ -579,19 +575,15 @@ void mam_amicphys_1subarea_clear( // associated aging), renaming, coagulation, and nucleation Real qgas_cur[num_gas_ids]; - for (int i = 0; i < num_gas_ids; ++i) - qgas_cur[i] = qgas3[i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_cur[i] = qgas3[i]; Real qaer_cur[num_aerosol_ids][num_modes]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) - qaer_cur[i][j] = qaer3[i][j]; + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_cur[i][j] = qaer3[i][j]; Real qnum_cur[num_modes]; - for (int j = 0; j < num_modes; ++j) - qnum_cur[j] = qnum3[j]; + for(int j = 0; j < num_modes; ++j) qnum_cur[j] = qnum3[j]; Real qwtr_cur[num_modes]; - for (int j = 0; j < num_modes; ++j) - qwtr_cur[j] = qwtr3[j]; + for(int j = 0; j < num_modes; ++j) qwtr_cur[j] = qwtr3[j]; // qgas_netprod_otrproc = gas net production rate from other processes // such as gas-phase chemistry and emissions (mol/mol/s) @@ -601,9 +593,9 @@ void mam_amicphys_1subarea_clear( // NOTE - must be >= zero, as numerical method can fail when it is negative // NOTE - currently only the values for h2so4 and nh3 should be non-zero Real qgas_netprod_otrproc[num_gas_ids] = {}; - if (config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { - for (int igas = 0; igas < num_gas_ids; ++igas) { - if (igas == igas_h2so4 || igas == igas_nh3) { + if(config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { + for(int igas = 0; igas < num_gas_ids; ++igas) { + if(igas == igas_h2so4 || igas == igas_nh3) { // if config.gaexch_h2so4_uptake_optaa == 2, then // if qgas increases from pre-gaschem to post-cldchem, // start from the pre-gaschem mix-ratio and add in the production @@ -612,71 +604,66 @@ void mam_amicphys_1subarea_clear( // start from post-cldchem mix-ratio // *** currently just do this for h2so4 and nh3 qgas_netprod_otrproc[igas] = (qgas3[igas] - qgas1[igas]) / deltat; - if (qgas_netprod_otrproc[igas] >= 0.0) + if(qgas_netprod_otrproc[igas] >= 0.0) qgas_cur[igas] = qgas1[igas]; else qgas_netprod_otrproc[igas] = 0.0; } } } - Real qgas_del_cond[num_gas_ids] = {}; - Real qgas_del_nnuc[num_gas_ids] = {}; - Real qgas_del_cond_only[num_gas_ids] = {}; - Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; - Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; + Real qgas_del_cond[num_gas_ids] = {}; + Real qgas_del_nnuc[num_gas_ids] = {}; + Real qgas_del_cond_only[num_gas_ids] = {}; + Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; + Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; + Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; + Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; Real qaer_delsub_coag_in[num_aerosol_ids][AeroConfig::max_agepair()] = {}; - Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; - Real qnum_del_cond[num_modes] = {}; - Real qnum_del_rnam[num_modes] = {}; - Real qnum_del_nnuc[num_modes] = {}; - Real qnum_del_coag[num_modes] = {}; - Real qnum_delsub_cond[num_modes] = {}; - Real qnum_delsub_coag[num_modes] = {}; - Real qnum_del_cond_only[num_modes] = {}; - Real dnclusterdt = 0.0; + Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; + Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; + Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; + Real qnum_del_cond[num_modes] = {}; + Real qnum_del_rnam[num_modes] = {}; + Real qnum_del_nnuc[num_modes] = {}; + Real qnum_del_coag[num_modes] = {}; + Real qnum_delsub_cond[num_modes] = {}; + Real qnum_delsub_coag[num_modes] = {}; + Real qnum_del_cond_only[num_modes] = {}; + Real dnclusterdt = 0.0; const int ntsubstep = 1; - Real dtsubstep = deltat; - if (ntsubstep > 1) - dtsubstep = deltat / ntsubstep; + Real dtsubstep = deltat; + if(ntsubstep > 1) dtsubstep = deltat / ntsubstep; Real del_h2so4_gasprod = haero::max(qgas3[igas_h2so4] - qgas1[igas_h2so4], 0.0) / ntsubstep; // loop over multiple time sub-steps - for (int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { + for(int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { // gas-aerosol exchange - Real uptkrate_h2so4 = 0.0; - Real del_h2so4_aeruptk = 0.0; + Real uptkrate_h2so4 = 0.0; + Real del_h2so4_aeruptk = 0.0; Real qaer_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; - Real qgas_avg[num_gas_ids] = {}; - Real qnum_sv1[num_modes] = {}; - Real qaer_sv1[num_aerosol_ids][num_modes] = {}; - Real qgas_sv1[num_gas_ids] = {}; - - if (config.do_cond) { + Real qgas_avg[num_gas_ids] = {}; + Real qnum_sv1[num_modes] = {}; + Real qaer_sv1[num_aerosol_ids][num_modes] = {}; + Real qgas_sv1[num_gas_ids] = {}; - const bool l_calc_gas_uptake_coeff = jtsubstep == 1; + if(config.do_cond) { + const bool l_calc_gas_uptake_coeff = jtsubstep == 1; Real uptkaer[num_gas_ids][num_modes] = {}; - for (int i = 0; i < num_gas_ids; ++i) - qgas_sv1[i] = qgas_cur[i]; - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_sv1[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; // time sub-step const Real dtsub_soa_fixed = -1.0; // Integration order - const int nghq = 2; + const int nghq = 2; const int ntot_soamode = 4; - int niter_out = 0; - Real g0_soa_out = 0; + int niter_out = 0; + Real g0_soa_out = 0; gasaerexch::mam_gasaerexch_1subarea( nghq, igas_h2so4, igas_nh3, ntot_soamode, gas_to_aer, iaer_so4, iaer_pom, l_calc_gas_uptake_coeff, l_gas_condense_to_mode, @@ -685,51 +672,50 @@ void mam_amicphys_1subarea_clear( qaer_cur, qnum_cur, dgn_awet, alnsg_aer, uptk_rate_factor, uptkaer, uptkrate_h2so4, niter_out, g0_soa_out); - if (config.newnuc_h2so4_conc_optaa == 11) + if(config.newnuc_h2so4_conc_optaa == 11) qgas_avg[igas_h2so4] = 0.5 * (qgas_sv1[igas_h2so4] + qgas_cur[igas_h2so4]); - else if (config.newnuc_h2so4_conc_optaa == 12) + else if(config.newnuc_h2so4_conc_optaa == 12) qgas_avg[igas_h2so4] = qgas_cur[igas_h2so4]; - for (int i = 0; i < num_gas_ids; ++i) + for(int i = 0; i < num_gas_ids; ++i) qgas_del_cond[i] += (qgas_cur[i] - (qgas_sv1[i] + qgas_netprod_otrproc[i] * dtsubstep)); - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_delsub_cond[i] = qnum_cur[i] - qnum_sv1[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_delsub_cond[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; // qaer_del_grow4rnam = change in qaer_del_cond during latest condensation // calculations - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_delsub_grow4rnam[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - for (int i = 0; i < num_gas_ids; ++i) + for(int i = 0; i < num_gas_ids; ++i) qgas_del_cond_only[i] = qgas_del_cond[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_del_cond_only[i][j] = qaer_delsub_cond[i][j]; - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_del_cond_only[i] = qnum_delsub_cond[i]; del_h2so4_aeruptk = qgas_cur[igas_h2so4] - (qgas_sv1[igas_h2so4] + qgas_netprod_otrproc[igas_h2so4] * dtsubstep); } else { - for (int i = 0; i < num_gas_ids; ++i) - qgas_avg[i] = qgas_cur[i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_avg[i] = qgas_cur[i]; } // renaming after "continuous growth" - if (config.do_rename) { - constexpr int nmodes = AeroConfig::num_modes(); - constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); - const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT + if(config.do_rename) { + constexpr int nmodes = AeroConfig::num_modes(); + constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); + const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT const int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; - Real qnumcw_cur[num_modes] = {}; - Real qaercw_cur[num_aerosol_ids][num_modes] = {}; + Real qnumcw_cur[num_modes] = {}; + Real qaercw_cur[num_aerosol_ids][num_modes] = {}; Real qaercw_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; Real mean_std_dev[nmodes]; Real fmode_dist_tail_fac[nmodes]; @@ -748,23 +734,21 @@ void mam_amicphys_1subarea_clear( 5.1923076923076926e-002, 156.20986883198000}; - rename::find_renaming_pairs(dest_mode_of_mode, // in - mean_std_dev, // out - fmode_dist_tail_fac, // out - v2n_lo_rlx, // out - v2n_hi_rlx, // out - ln_diameter_tail_fac, // out - num_pairs, // out - diameter_cutoff, // out + rename::find_renaming_pairs(dest_mode_of_mode, // in + mean_std_dev, // out + fmode_dist_tail_fac, // out + v2n_lo_rlx, // out + v2n_hi_rlx, // out + ln_diameter_tail_fac, // out + num_pairs, // out + diameter_cutoff, // out ln_dia_cutoff, diameter_threshold); - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_sv1[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; Real dgnum_amode[nmodes]; - for (int m = 0; m < nmodes; ++m) { + for(int m = 0; m < nmodes; ++m) { dgnum_amode[m] = modes(m).nom_diameter; } @@ -773,8 +757,8 @@ void mam_amicphys_1subarea_clear( Real qmol_i_del[num_modes][num_aerosol_ids]; Real qmol_c_cur[num_modes][num_aerosol_ids]; Real qmol_c_del[num_modes][num_aerosol_ids]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) { + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) { qmol_i_cur[i][j] = qaer_cur[j][i]; qmol_i_del[i][j] = qaer_delsub_grow4rnam[j][i]; qmol_c_cur[i][j] = qaercw_cur[j][i]; @@ -788,44 +772,42 @@ void mam_amicphys_1subarea_clear( diameter_threshold, mass_2_vol, dgnum_amode, qnum_cur, qmol_i_cur, qmol_i_del, qnumcw_cur, qmol_c_cur, qmol_c_del); - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) { - qaer_cur[j][i] = qmol_i_cur[i][j]; - qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; - qaercw_cur[j][i] = qmol_c_cur[i][j]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) { + qaer_cur[j][i] = qmol_i_cur[i][j]; + qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; + qaercw_cur[j][i] = qmol_c_cur[i][j]; qaercw_delsub_grow4rnam[j][i] = qmol_c_del[i][j]; } } - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_del_rnam[i] += qnum_cur[i] - qnum_sv1[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_del_rnam[i][j] += qaer_cur[i][j] - qaer_sv1[i][j]; } // new particle formation (nucleation) - if (config.do_newnuc) { - for (int i = 0; i < num_gas_ids; ++i) - qgas_sv1[i] = qgas_cur[i]; - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; + if(config.do_newnuc) { + for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; Real qaer_cur_tmp[num_modes][num_aerosol_ids]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) { - qaer_sv1[j][i] = qaer_cur[j][i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) { + qaer_sv1[j][i] = qaer_cur[j][i]; qaer_cur_tmp[i][j] = qaer_cur[j][i]; } Real dnclusterdt_substep = 0; - Real dndt_ait = 0; - Real dmdt_ait = 0; - Real dso4dt_ait = 0; - Real dnh4dt_ait = 0; + Real dndt_ait = 0; + Real dmdt_ait = 0; + Real dso4dt_ait = 0; + Real dnh4dt_ait = 0; Nucleation nucleation; Nucleation::Config config; - config.dens_so4a_host = 1770; - config.mw_nh4a_host = 115; - config.mw_so4a_host = 115; + config.dens_so4a_host = 1770; + config.mw_nh4a_host = 115; + config.mw_so4a_host = 115; config.accom_coef_h2so4 = 0.65; AeroConfig aero_config; nucleation.init(aero_config, config); @@ -834,16 +816,15 @@ void mam_amicphys_1subarea_clear( del_h2so4_gasprod, del_h2so4_aeruptk, qgas_cur, qgas_avg, qnum_cur, qaer_cur_tmp, qwtr_cur, dndt_ait, dmdt_ait, dso4dt_ait, dnh4dt_ait, dnclusterdt_substep); - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_cur[j][i] = qaer_cur_tmp[i][j]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_cur[j][i] = qaer_cur_tmp[i][j]; //! Apply the tendencies to the prognostics. const int nait = static_cast(ModeIndex::Aitken); qnum_cur[nait] += dndt_ait * dtsubstep; - if (dso4dt_ait > 0.0) { - static constexpr int iaer_so4 = static_cast(AeroId::SO4); + if(dso4dt_ait > 0.0) { + static constexpr int iaer_so4 = static_cast(AeroId::SO4); static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); Real delta_q = dso4dt_ait * dtsubstep; @@ -852,40 +833,38 @@ void mam_amicphys_1subarea_clear( qgas_cur[igas_h2so4] -= delta_q; } - if (igas_nh3 > 0 && dnh4dt_ait > 0.0) { + if(igas_nh3 > 0 && dnh4dt_ait > 0.0) { static constexpr int iaer_nh4 = - -9999999; // static_cast(AeroId::NH4); + -9999999; // static_cast(AeroId::NH4); Real delta_q = dnh4dt_ait * dtsubstep; qaer_cur[iaer_nh4][nait] += delta_q; delta_q = haero::min(delta_q, qgas_cur[igas_nh3]); qgas_cur[igas_nh3] -= delta_q; } - for (int i = 0; i < num_gas_ids; ++i) + for(int i = 0; i < num_gas_ids; ++i) qgas_del_nnuc[i] += (qgas_cur[i] - qgas_sv1[i]); - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_del_nnuc[i] += (qnum_cur[i] - qnum_sv1[i]); - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_del_nnuc[j][i] += (qaer_cur[j][i] - qaer_sv1[j][i]); dnclusterdt = dnclusterdt + dnclusterdt_substep * (dtsubstep / deltat); } // coagulation part - if (config.do_coag) { - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_sv1[j][i] = qaer_cur[j][i]; + if(config.do_coag) { + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; coagulation::mam_coag_1subarea(dtsubstep, temp, pmid, aircon, dgn_a, dgn_awet, wetdens, qnum_cur, qaer_cur, qaer_delsub_coag_in); - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_delsub_coag[i] = qnum_cur[i] - qnum_sv1[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_delsub_coag[j][i] = qaer_cur[j][i] - qaer_sv1[j][i]; } @@ -896,54 +875,50 @@ void mam_amicphys_1subarea_clear( qaer_delsub_cond, qaer_delsub_coag, qaer_delsub_coag_in); // accumulate sub-step q-dels - if (config.do_coag) { - for (int i = 0; i < num_modes; ++i) + if(config.do_coag) { + for(int i = 0; i < num_modes; ++i) qnum_del_coag[i] += qnum_delsub_coag[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_del_coag[j][i] += qaer_delsub_coag[j][i]; } - if (config.do_cond) { - for (int i = 0; i < num_modes; ++i) + if(config.do_cond) { + for(int i = 0; i < num_modes; ++i) qnum_del_cond[i] += qnum_delsub_cond[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_del_cond[j][i] += qaer_delsub_cond[j][i]; } } // final mix ratios - for (int i = 0; i < num_gas_ids; ++i) - qgas4[i] = qgas_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer4[j][i] = qaer_cur[j][i]; - for (int i = 0; i < num_modes; ++i) - qnum4[i] = qnum_cur[i]; - for (int i = 0; i < num_modes; ++i) - qwtr4[i] = qwtr_cur[i]; + for(int i = 0; i < num_gas_ids; ++i) qgas4[i] = qgas_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer4[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_modes; ++i) qnum4[i] = qnum_cur[i]; + for(int i = 0; i < num_modes; ++i) qwtr4[i] = qwtr_cur[i]; // final mix ratio changes - for (int i = 0; i < num_gas_ids; ++i) { - qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; - qgas_delaa[i][iqtend_rnam()] = 0.0; - qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; - qgas_delaa[i][iqtend_coag()] = 0.0; + for(int i = 0; i < num_gas_ids; ++i) { + qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; + qgas_delaa[i][iqtend_rnam()] = 0.0; + qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; + qgas_delaa[i][iqtend_coag()] = 0.0; qgas_delaa[i][iqtend_cond_only()] = qgas_del_cond_only[i]; } - for (int i = 0; i < num_modes; ++i) { - qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; - qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; - qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; - qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; + for(int i = 0; i < num_modes; ++i) { + qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; + qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; + qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; + qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; qnum_delaa[i][iqtend_cond_only()] = qnum_del_cond_only[i]; } - for (int j = 0; j < num_aerosol_ids; ++j) { - for (int i = 0; i < num_modes; ++i) { - qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; - qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; - qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; - qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; + for(int j = 0; j < num_aerosol_ids; ++j) { + for(int i = 0; i < num_modes; ++i) { + qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; + qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; + qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; + qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; qaer_delaa[j][i][iqtend_cond_only()] = qaer_del_cond_only[j][i]; } } @@ -951,11 +926,11 @@ void mam_amicphys_1subarea_clear( KOKKOS_INLINE_FUNCTION void mam_amicphys_1subarea_cloudy( - const AmicPhysConfig& config, const int nstep, const Real deltat, const int jsub, - const int nsubarea, const bool iscldy_subarea, const Real afracsub, - const Real temp, const Real pmid, const Real pdel, const Real zmid, - const Real pblh, const Real relhum, Real dgn_a[AeroConfig::num_modes()], - Real dgn_awet[AeroConfig::num_modes()], + const AmicPhysConfig &config, const int nstep, const Real deltat, + const int jsub, const int nsubarea, const bool iscldy_subarea, + const Real afracsub, const Real temp, const Real pmid, const Real pdel, + const Real zmid, const Real pblh, const Real relhum, + Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], Real wetdens[AeroConfig::num_modes()], const Real qgas1[AeroConfig::num_gas_ids()], const Real qgas3[AeroConfig::num_gas_ids()], @@ -979,7 +954,6 @@ void mam_amicphys_1subarea_cloudy( Real qaercw4[AeroConfig::num_gas_ids()][AeroConfig::num_modes()], Real qaercw_delaa[AeroConfig::num_gas_ids()][AeroConfig::num_modes()] [nqqcwtendaa()]) { - // // calculates changes to gas and aerosol sub-area TMRs (tracer mixing ratios) // qgas3, qaer3, qaercw3, qnum3, qnumcw3 are the current incoming TMRs @@ -1019,13 +993,13 @@ void mam_amicphys_1subarea_cloudy( // qxxx_del_yyyy are mix-ratio changes over full time step (deltat) // qxxx_delsub_yyyy are mix-ratio changes over time sub-step (dtsubstep) - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); + static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); + static constexpr int num_modes = AeroConfig::num_modes(); static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); // Turn off nh3 for now. This is a future enhancement. - static constexpr int igas_nh3 = -999888777; // Same as mam_refactor + static constexpr int igas_nh3 = -999888777; // Same as mam_refactor static constexpr int iaer_so4 = static_cast(AeroId::SO4); static constexpr int iaer_pom = static_cast(AeroId::POM); @@ -1041,40 +1015,34 @@ void mam_amicphys_1subarea_cloudy( // In order to try to match the results in mam_refactor // set r_universal as [mJ/(mol)] as in mam_refactor. // const Real r_universal = Constants::r_gas; // [mJ/(K mol)] - const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor - const Real aircon = pmid / (1000 * r_universal * temp); + const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor + const Real aircon = pmid / (1000 * r_universal * temp); const Real alnsg_aer[num_modes] = {0.58778666490211906, 0.47000362924573563, 0.58778666490211906, 0.47000362924573563}; const Real uptk_rate_factor[num_gas_ids] = {0.81, 1.0, 1.0}; Real qgas_cur[num_gas_ids]; - for (int i = 0; i < num_gas_ids; ++i) - qgas_cur[i] = qgas3[i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_cur[i] = qgas3[i]; Real qaer_cur[num_aerosol_ids][num_modes]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) - qaer_cur[i][j] = qaer3[i][j]; + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_cur[i][j] = qaer3[i][j]; Real qnum_cur[num_modes]; - for (int j = 0; j < num_modes; ++j) - qnum_cur[j] = qnum3[j]; + for(int j = 0; j < num_modes; ++j) qnum_cur[j] = qnum3[j]; Real qwtr_cur[num_modes]; - for (int j = 0; j < num_modes; ++j) - qwtr_cur[j] = qwtr3[j]; + for(int j = 0; j < num_modes; ++j) qwtr_cur[j] = qwtr3[j]; Real qnumcw_cur[num_modes]; - for (int j = 0; j < num_modes; ++j) - qnumcw_cur[j] = qnumcw3[j]; + for(int j = 0; j < num_modes; ++j) qnumcw_cur[j] = qnumcw3[j]; Real qaercw_cur[num_gas_ids][num_modes]; - for (int i = 0; i < num_gas_ids; ++i) - for (int j = 0; j < num_modes; ++j) - qaercw_cur[i][j] = qaercw3[i][j]; + for(int i = 0; i < num_gas_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw_cur[i][j] = qaercw3[i][j]; Real qgas_netprod_otrproc[num_gas_ids] = {}; - if (config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { - for (int igas = 0; igas < num_gas_ids; ++igas) { - if (igas == igas_h2so4 || igas == igas_nh3) { + if(config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { + for(int igas = 0; igas < num_gas_ids; ++igas) { + if(igas == igas_h2so4 || igas == igas_nh3) { // if gaexch_h2so4_uptake_optaa == 2, then // if qgas increases from pre-gaschem to post-cldchem, // start from the pre-gaschem mix-ratio and add in the production @@ -1083,67 +1051,62 @@ void mam_amicphys_1subarea_cloudy( // start from post-cldchem mix-ratio // *** currently just do this for h2so4 and nh3 qgas_netprod_otrproc[igas] = (qgas3[igas] - qgas1[igas]) / deltat; - if (qgas_netprod_otrproc[igas] >= 0.0) + if(qgas_netprod_otrproc[igas] >= 0.0) qgas_cur[igas] = qgas1[igas]; else qgas_netprod_otrproc[igas] = 0.0; } } } - Real qgas_del_cond[num_gas_ids] = {}; - Real qgas_del_nnuc[num_gas_ids] = {}; - Real qgas_del_cond_only[num_gas_ids] = {}; - Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; - Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; - Real qaercw_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qnum_del_cond[num_modes] = {}; - Real qnum_del_rnam[num_modes] = {}; - Real qnum_del_nnuc[num_modes] = {}; - Real qnum_del_coag[num_modes] = {}; - Real qnum_delsub_cond[num_modes] = {}; - Real qnum_delsub_coag[num_modes] = {}; - Real qnum_del_cond_only[num_modes] = {}; - Real qnumcw_del_rnam[num_modes] = {}; + Real qgas_del_cond[num_gas_ids] = {}; + Real qgas_del_nnuc[num_gas_ids] = {}; + Real qgas_del_cond_only[num_gas_ids] = {}; + Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; + Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; + Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; + Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; + Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; + Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; + Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; + Real qaercw_del_rnam[num_aerosol_ids][num_modes] = {}; + Real qnum_del_cond[num_modes] = {}; + Real qnum_del_rnam[num_modes] = {}; + Real qnum_del_nnuc[num_modes] = {}; + Real qnum_del_coag[num_modes] = {}; + Real qnum_delsub_cond[num_modes] = {}; + Real qnum_delsub_coag[num_modes] = {}; + Real qnum_del_cond_only[num_modes] = {}; + Real qnumcw_del_rnam[num_modes] = {}; Real qaer_delsub_coag_in[num_aerosol_ids][AeroConfig::max_agepair()] = {}; const int ntsubstep = 1; - Real dtsubstep = deltat; - if (ntsubstep > 1) - dtsubstep = deltat / ntsubstep; + Real dtsubstep = deltat; + if(ntsubstep > 1) dtsubstep = deltat / ntsubstep; // loop over multiple time sub-steps - for (int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { + for(int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { // gas-aerosol exchange - Real uptkrate_h2so4 = 0.0; - Real qgas_avg[num_gas_ids] = {}; - Real qgas_sv1[num_gas_ids] = {}; - Real qnum_sv1[num_modes] = {}; - Real qaer_sv1[num_aerosol_ids][num_modes] = {}; + Real uptkrate_h2so4 = 0.0; + Real qgas_avg[num_gas_ids] = {}; + Real qgas_sv1[num_gas_ids] = {}; + Real qnum_sv1[num_modes] = {}; + Real qaer_sv1[num_aerosol_ids][num_modes] = {}; Real qaer_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; - if (config.do_cond) { - - const bool l_calc_gas_uptake_coeff = jtsubstep == 1; + if(config.do_cond) { + const bool l_calc_gas_uptake_coeff = jtsubstep == 1; Real uptkaer[num_gas_ids][num_modes] = {}; - for (int i = 0; i < num_gas_ids; ++i) - qgas_sv1[i] = qgas_cur[i]; - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_sv1[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; - const int nghq = 2; + const int nghq = 2; const int ntot_soamode = 4; - int niter_out = 0; - Real g0_soa_out = 0; + int niter_out = 0; + Real g0_soa_out = 0; // time sub-step const Real dtsub_soa_fixed = -1.0; gasaerexch::mam_gasaerexch_1subarea( @@ -1154,48 +1117,47 @@ void mam_amicphys_1subarea_cloudy( qaer_cur, qnum_cur, dgn_awet, alnsg_aer, uptk_rate_factor, uptkaer, uptkrate_h2so4, niter_out, g0_soa_out); - if (config.newnuc_h2so4_conc_optaa == 11) + if(config.newnuc_h2so4_conc_optaa == 11) qgas_avg[igas_h2so4] = 0.5 * (qgas_sv1[igas_h2so4] + qgas_cur[igas_h2so4]); - else if (config.newnuc_h2so4_conc_optaa == 12) + else if(config.newnuc_h2so4_conc_optaa == 12) qgas_avg[igas_h2so4] = qgas_cur[igas_h2so4]; - for (int i = 0; i < num_gas_ids; ++i) + for(int i = 0; i < num_gas_ids; ++i) qgas_del_cond[i] += (qgas_cur[i] - (qgas_sv1[i] + qgas_netprod_otrproc[i] * dtsubstep)); - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_delsub_cond[i] = qnum_cur[i] - qnum_sv1[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_delsub_cond[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; // qaer_del_grow4rnam = change in qaer_del_cond during latest condensation // calculations - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_delsub_grow4rnam[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - for (int i = 0; i < num_gas_ids; ++i) + for(int i = 0; i < num_gas_ids; ++i) qgas_del_cond_only[i] = qgas_del_cond[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_del_cond_only[i][j] = qaer_delsub_cond[i][j]; - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_del_cond_only[i] = qnum_delsub_cond[i]; } else { - for (int i = 0; i < num_gas_ids; ++i) - qgas_avg[i] = qgas_cur[i]; + for(int i = 0; i < num_gas_ids; ++i) qgas_avg[i] = qgas_cur[i]; } // renaming after "continuous growth" - if (config.do_rename) { - constexpr int nmodes = AeroConfig::num_modes(); - constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); - const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT + if(config.do_rename) { + constexpr int nmodes = AeroConfig::num_modes(); + constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); + const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT const int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; - Real qnumcw_cur[num_modes] = {}; - Real qaercw_cur[num_aerosol_ids][num_modes] = {}; + Real qnumcw_cur[num_modes] = {}; + Real qaercw_cur[num_aerosol_ids][num_modes] = {}; Real qaercw_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; Real mean_std_dev[nmodes]; Real fmode_dist_tail_fac[nmodes]; @@ -1214,46 +1176,42 @@ void mam_amicphys_1subarea_cloudy( 5.1923076923076926e-002, 156.20986883198000}; - rename::find_renaming_pairs(dest_mode_of_mode, // in - mean_std_dev, // out - fmode_dist_tail_fac, // out - v2n_lo_rlx, // out - v2n_hi_rlx, // out - ln_diameter_tail_fac, // out - num_pairs, // out - diameter_cutoff, // out + rename::find_renaming_pairs(dest_mode_of_mode, // in + mean_std_dev, // out + fmode_dist_tail_fac, // out + v2n_lo_rlx, // out + v2n_hi_rlx, // out + ln_diameter_tail_fac, // out + num_pairs, // out + diameter_cutoff, // out ln_dia_cutoff, diameter_threshold); - for (int i = 0; i < num_modes; ++i) - qnum_sv1[i] = qnum_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer_sv1[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; Real dgnum_amode[nmodes]; - for (int m = 0; m < nmodes; ++m) { + for(int m = 0; m < nmodes; ++m) { dgnum_amode[m] = modes(m).nom_diameter; } // qaercw_delsub_grow4rnam = change in qaercw from cloud chemistry - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw_delsub_grow4rnam[i][j] = (qaercw3[i][j] - qaercw2[i][j]) / ntsubstep; Real qnumcw_sv1[num_modes]; - for (int i = 0; i < num_modes; ++i) - qnumcw_sv1[i] = qnumcw_cur[i]; + for(int i = 0; i < num_modes; ++i) qnumcw_sv1[i] = qnumcw_cur[i]; Real qaercw_sv1[num_aerosol_ids][num_modes]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) - qaercw_sv1[i][j] = qaercw_cur[i][j]; + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw_sv1[i][j] = qaercw_cur[i][j]; { Real qmol_i_cur[num_modes][num_aerosol_ids]; Real qmol_i_del[num_modes][num_aerosol_ids]; Real qmol_c_cur[num_modes][num_aerosol_ids]; Real qmol_c_del[num_modes][num_aerosol_ids]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) { + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) { qmol_i_cur[i][j] = qaer_cur[j][i]; qmol_i_del[i][j] = qaer_delsub_grow4rnam[j][i]; qmol_c_cur[i][j] = qaercw_cur[j][i]; @@ -1268,96 +1226,90 @@ void mam_amicphys_1subarea_cloudy( diameter_threshold, mass_2_vol, dgnum_amode, qnum_cur, qmol_i_cur, qmol_i_del, qnumcw_cur, qmol_c_cur, qmol_c_del); - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) { - qaer_cur[j][i] = qmol_i_cur[i][j]; - qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; - qaercw_cur[j][i] = qmol_c_cur[i][j]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) { + qaer_cur[j][i] = qmol_i_cur[i][j]; + qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; + qaercw_cur[j][i] = qmol_c_cur[i][j]; qaercw_delsub_grow4rnam[j][i] = qmol_c_del[i][j]; } } - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnum_del_rnam[i] += qnum_cur[i] - qnum_sv1[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaer_del_rnam[i][j] += qaer_cur[i][j] - qaer_sv1[i][j]; - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnumcw_del_rnam[i] += qnumcw_cur[i] - qnumcw_sv1[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw_del_rnam[i][j] += qaercw_cur[i][j] - qaercw_sv1[i][j]; } // primary carbon aging - if (config.do_cond) { + if(config.do_cond) { aging::mam_pcarbon_aging_1subarea( dgn_a, qnum_cur, qnum_delsub_cond, qnum_delsub_coag, qaer_cur, qaer_delsub_cond, qaer_delsub_coag, qaer_delsub_coag_in); } // accumulate sub-step q-dels - if (config.do_cond) { - for (int i = 0; i < num_modes; ++i) + if(config.do_cond) { + for(int i = 0; i < num_modes; ++i) qnum_del_cond[i] += qnum_delsub_cond[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer_del_cond[j][i] += qaer_delsub_cond[j][i]; } } // final mix ratios - for (int i = 0; i < num_gas_ids; ++i) - qgas4[i] = qgas_cur[i]; - for (int j = 0; j < num_aerosol_ids; ++j) - for (int i = 0; i < num_modes; ++i) - qaer4[j][i] = qaer_cur[j][i]; - for (int i = 0; i < num_modes; ++i) - qnum4[i] = qnum_cur[i]; - for (int i = 0; i < num_modes; ++i) - qwtr4[i] = qwtr_cur[i]; - for (int i = 0; i < num_modes; ++i) - qnumcw4[i] = qnumcw_cur[i]; - for (int i = 0; i < num_gas_ids; ++i) - for (int j = 0; j < num_modes; ++j) - qaercw4[i][j] = qaercw_cur[i][j]; + for(int i = 0; i < num_gas_ids; ++i) qgas4[i] = qgas_cur[i]; + for(int j = 0; j < num_aerosol_ids; ++j) + for(int i = 0; i < num_modes; ++i) qaer4[j][i] = qaer_cur[j][i]; + for(int i = 0; i < num_modes; ++i) qnum4[i] = qnum_cur[i]; + for(int i = 0; i < num_modes; ++i) qwtr4[i] = qwtr_cur[i]; + for(int i = 0; i < num_modes; ++i) qnumcw4[i] = qnumcw_cur[i]; + for(int i = 0; i < num_gas_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw4[i][j] = qaercw_cur[i][j]; // final mix ratio changes - for (int i = 0; i < num_gas_ids; ++i) { - qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; - qgas_delaa[i][iqtend_rnam()] = 0.0; - qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; - qgas_delaa[i][iqtend_coag()] = 0.0; + for(int i = 0; i < num_gas_ids; ++i) { + qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; + qgas_delaa[i][iqtend_rnam()] = 0.0; + qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; + qgas_delaa[i][iqtend_coag()] = 0.0; qgas_delaa[i][iqtend_cond_only()] = qgas_del_cond_only[i]; } - for (int i = 0; i < num_modes; ++i) { - qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; - qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; - qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; - qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; + for(int i = 0; i < num_modes; ++i) { + qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; + qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; + qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; + qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; qnum_delaa[i][iqtend_cond_only()] = qnum_del_cond_only[i]; } - for (int j = 0; j < num_aerosol_ids; ++j) { - for (int i = 0; i < num_modes; ++i) { - qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; - qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; - qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; - qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; + for(int j = 0; j < num_aerosol_ids; ++j) { + for(int i = 0; i < num_modes; ++i) { + qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; + qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; + qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; + qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; qaer_delaa[j][i][iqtend_cond_only()] = qaer_del_cond_only[j][i]; } } - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) qnumcw_delaa[i][iqqcwtend_rnam()] = qnumcw_del_rnam[i]; - for (int i = 0; i < num_aerosol_ids; ++i) - for (int j = 0; j < num_modes; ++j) + for(int i = 0; i < num_aerosol_ids; ++i) + for(int j = 0; j < num_modes; ++j) qaercw_delaa[i][j][iqqcwtend_rnam()] = qaercw_del_rnam[i][j]; } KOKKOS_INLINE_FUNCTION void mam_amicphys_1gridcell( - const AmicPhysConfig& config, const int nstep, const Real deltat, const int nsubarea, - const int ncldy_subarea, const bool iscldy_subarea[maxsubarea()], - const Real afracsub[maxsubarea()], const Real temp, const Real pmid, - const Real pdel, const Real zmid, const Real pblh, - const Real relhumsub[maxsubarea()], Real dgn_a[AeroConfig::num_modes()], - Real dgn_awet[AeroConfig::num_modes()], + const AmicPhysConfig &config, const int nstep, const Real deltat, + const int nsubarea, const int ncldy_subarea, + const bool iscldy_subarea[maxsubarea()], const Real afracsub[maxsubarea()], + const Real temp, const Real pmid, const Real pdel, const Real zmid, + const Real pblh, const Real relhumsub[maxsubarea()], + Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], Real wetdens[AeroConfig::num_modes()], const Real qsub1[AeroConfig::num_gas_ids()][maxsubarea()], const Real qsub2[AeroConfig::num_gas_ids()][maxsubarea()], @@ -1369,8 +1321,8 @@ void mam_amicphys_1gridcell( Real qqcwsub4[AeroConfig::num_gas_ids()][maxsubarea()], Real qaerwatsub4[AeroConfig::num_modes()][maxsubarea()], Real qsub_tendaa[AeroConfig::num_gas_ids()][nqtendaa()][maxsubarea()], - Real qqcwsub_tendaa[AeroConfig::num_gas_ids()][nqqcwtendaa()][maxsubarea()]) { - + Real qqcwsub_tendaa[AeroConfig::num_gas_ids()][nqqcwtendaa()] + [maxsubarea()]) { // // calculates changes to gas and aerosol sub-area TMRs (tracer mixing ratios) // qsub3 and qqcwsub3 are the incoming current TMRs @@ -1389,35 +1341,32 @@ void mam_amicphys_1gridcell( // the processes are condensation/evaporation (and associated aging), // renaming, coagulation, and nucleation - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); + static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); + static constexpr int num_modes = AeroConfig::num_modes(); static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); // the q--4 values will be equal to q--3 values unless they get changed - for (int i = 0; i < num_gas_ids; ++i) - for (int j = 0; j < maxsubarea(); ++j) { - qsub4[i][j] = qsub3[i][j]; + for(int i = 0; i < num_gas_ids; ++i) + for(int j = 0; j < maxsubarea(); ++j) { + qsub4[i][j] = qsub3[i][j]; qqcwsub4[i][j] = qqcwsub3[i][j]; } - for (int i = 0; i < num_modes; ++i) - for (int j = 0; j < maxsubarea(); ++j) - qaerwatsub4[i][j] = qaerwatsub3[i][j]; - for (int i = 0; i < num_gas_ids; ++i) - for (int j = 0; j < nqtendaa(); ++j) - for (int k = 0; k < maxsubarea(); ++k) - qsub_tendaa[i][j][k] = 0; - for (int i = 0; i < num_gas_ids; ++i) - for (int j = 0; j < nqqcwtendaa(); ++j) - for (int k = 0; k < maxsubarea(); ++k) - qqcwsub_tendaa[i][j][k] = 0.0; - - for (int jsub = 0; jsub < nsubarea; ++jsub) { + for(int i = 0; i < num_modes; ++i) + for(int j = 0; j < maxsubarea(); ++j) qaerwatsub4[i][j] = qaerwatsub3[i][j]; + for(int i = 0; i < num_gas_ids; ++i) + for(int j = 0; j < nqtendaa(); ++j) + for(int k = 0; k < maxsubarea(); ++k) qsub_tendaa[i][j][k] = 0; + for(int i = 0; i < num_gas_ids; ++i) + for(int j = 0; j < nqqcwtendaa(); ++j) + for(int k = 0; k < maxsubarea(); ++k) qqcwsub_tendaa[i][j][k] = 0.0; + + for(int jsub = 0; jsub < nsubarea; ++jsub) { AmicPhysConfig sub_config = config; - if (iscldy_subarea[jsub]) { - sub_config.do_cond = config.do_cond; + if(iscldy_subarea[jsub]) { + sub_config.do_cond = config.do_cond; sub_config.do_rename = config.do_rename; sub_config.do_newnuc = false; - sub_config.do_coag = false; + sub_config.do_coag = false; } const bool do_map_gas_sub = sub_config.do_cond || sub_config.do_newnuc; @@ -1425,9 +1374,9 @@ void mam_amicphys_1gridcell( Real qgas1[num_gas_ids] = {}; Real qgas3[num_gas_ids] = {}; Real qgas4[num_gas_ids] = {}; - if (do_map_gas_sub) { + if(do_map_gas_sub) { // for cldy subarea, only do gases if doing gaexch - for (int igas = 0; igas < 2; ++igas) { + for(int igas = 0; igas < 2; ++igas) { const int l = lmap_gas(igas); qgas1[igas] = qsub1[l][jsub] * fcvt_gas(igas); qgas3[igas] = qsub3[l][jsub] * fcvt_gas(igas); @@ -1436,15 +1385,15 @@ void mam_amicphys_1gridcell( } Real qaer2[num_aerosol_ids][num_modes] = {}; Real qaer3[num_aerosol_ids][num_modes] = {}; - Real qnum3[num_modes] = {}; + Real qnum3[num_modes] = {}; Real qaer4[num_aerosol_ids][num_modes] = {}; - Real qnum4[num_modes] = {}; - Real qwtr3[num_modes] = {}; - Real qwtr4[num_modes] = {}; - for (int n = 0; n < num_modes; ++n) { + Real qnum4[num_modes] = {}; + Real qwtr3[num_modes] = {}; + Real qwtr4[num_modes] = {}; + for(int n = 0; n < num_modes; ++n) { qnum3[n] = qsub3[n][jsub] * fcvt_num(); qnum4[n] = qnum3[n]; - for (int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { qaer2[iaer][n] = qsub2[iaer][jsub] * fcvt_aer(iaer); qaer3[iaer][n] = qsub3[iaer][jsub] * fcvt_aer(iaer); qaer4[iaer][n] = qaer3[iaer][n]; @@ -1454,15 +1403,15 @@ void mam_amicphys_1gridcell( } Real qaercw2[num_aerosol_ids][num_modes] = {}; Real qaercw3[num_aerosol_ids][num_modes] = {}; - Real qnumcw3[num_modes] = {}; + Real qnumcw3[num_modes] = {}; Real qaercw4[num_aerosol_ids][num_modes] = {}; - Real qnumcw4[num_modes] = {}; - if (iscldy_subarea[jsub]) { + Real qnumcw4[num_modes] = {}; + if(iscldy_subarea[jsub]) { // only do cloud-borne for cloudy - for (int n = 0; n < num_modes; ++n) { + for(int n = 0; n < num_modes; ++n) { qnumcw3[n] = qqcwsub3[n][jsub] * fcvt_num(); qnumcw4[n] = qnumcw3[n]; - for (int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { qaercw2[iaer][n] = qqcwsub2[n][jsub] * fcvt_aer(iaer); qaercw3[iaer][n] = qqcwsub3[n][jsub] * fcvt_aer(iaer); qaercw4[iaer][n] = qaercw3[iaer][n]; @@ -1470,57 +1419,57 @@ void mam_amicphys_1gridcell( } } - Real qgas_delaa[num_gas_ids][nqtendaa()] = {}; - Real qnum_delaa[num_modes][nqtendaa()] = {}; - Real qnumcw_delaa[num_modes][nqqcwtendaa()] = {}; - Real qaer_delaa[num_aerosol_ids][num_modes][nqtendaa()] = {}; + Real qgas_delaa[num_gas_ids][nqtendaa()] = {}; + Real qnum_delaa[num_modes][nqtendaa()] = {}; + Real qnumcw_delaa[num_modes][nqqcwtendaa()] = {}; + Real qaer_delaa[num_aerosol_ids][num_modes][nqtendaa()] = {}; Real qaercw_delaa[num_aerosol_ids][num_modes][nqqcwtendaa()] = {}; - if (iscldy_subarea[jsub]) { - mam_amicphys_1subarea_cloudy(sub_config, nstep, deltat, - jsub, nsubarea, iscldy_subarea[jsub], afracsub[jsub], temp, pmid, - pdel, zmid, pblh, relhumsub[jsub], dgn_a, dgn_awet, wetdens, qgas1, - qgas3, qgas4, qgas_delaa, qnum3, qnum4, qnum_delaa, qaer2, qaer3, - qaer4, qaer_delaa, qwtr3, qwtr4, qnumcw3, qnumcw4, qnumcw_delaa, - qaercw2, qaercw3, qaercw4, qaercw_delaa); + if(iscldy_subarea[jsub]) { + mam_amicphys_1subarea_cloudy( + sub_config, nstep, deltat, jsub, nsubarea, iscldy_subarea[jsub], + afracsub[jsub], temp, pmid, pdel, zmid, pblh, relhumsub[jsub], dgn_a, + dgn_awet, wetdens, qgas1, qgas3, qgas4, qgas_delaa, qnum3, qnum4, + qnum_delaa, qaer2, qaer3, qaer4, qaer_delaa, qwtr3, qwtr4, qnumcw3, + qnumcw4, qnumcw_delaa, qaercw2, qaercw3, qaercw4, qaercw_delaa); } else { - mam_amicphys_1subarea_clear(sub_config, nstep, deltat, - jsub, nsubarea, iscldy_subarea[jsub], afracsub[jsub], temp, pmid, - pdel, zmid, pblh, relhumsub[jsub], dgn_a, dgn_awet, wetdens, qgas1, - qgas3, qgas4, qgas_delaa, qnum3, qnum4, qnum_delaa, qaer3, qaer4, - qaer_delaa, qwtr3, qwtr4); + mam_amicphys_1subarea_clear( + sub_config, nstep, deltat, jsub, nsubarea, iscldy_subarea[jsub], + afracsub[jsub], temp, pmid, pdel, zmid, pblh, relhumsub[jsub], dgn_a, + dgn_awet, wetdens, qgas1, qgas3, qgas4, qgas_delaa, qnum3, qnum4, + qnum_delaa, qaer3, qaer4, qaer_delaa, qwtr3, qwtr4); // map gas/aer/num arrays (mix-ratio and del=change) back to sub-area // arrays - if (do_map_gas_sub) { - for (int igas = 0; igas < 2; ++igas) { - const int l = lmap_gas(igas); + if(do_map_gas_sub) { + for(int igas = 0; igas < 2; ++igas) { + const int l = lmap_gas(igas); qsub4[l][jsub] = qgas4[igas] / fcvt_gas(igas); - for (int i = 0; i < nqtendaa(); ++i) + for(int i = 0; i < nqtendaa(); ++i) qsub_tendaa[l][i][jsub] = qgas_delaa[igas][i] / (fcvt_gas(igas) * deltat); } } - for (int n = 0; n < num_modes; ++n) { + for(int n = 0; n < num_modes; ++n) { qsub4[n][jsub] = qnum4[n] / fcvt_num(); - for (int i = 0; i < nqtendaa(); ++i) + for(int i = 0; i < nqtendaa(); ++i) qsub_tendaa[n][i][jsub] = qnum_delaa[n][i] / (fcvt_num() * deltat); - for (int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { qsub4[iaer][jsub] = qaer4[iaer][n] / fcvt_aer(iaer); - for (int i = 0; i < nqtendaa(); ++i) + for(int i = 0; i < nqtendaa(); ++i) qsub_tendaa[iaer][i][jsub] = qaer_delaa[iaer][n][i] / (fcvt_aer(iaer) * deltat); } qaerwatsub4[n][jsub] = qwtr4[n] / fcvt_wtr(); - if (iscldy_subarea[jsub]) { + if(iscldy_subarea[jsub]) { qqcwsub4[n][jsub] = qnumcw4[n] / fcvt_num(); - for (int i = 0; i < nqqcwtendaa(); ++i) + for(int i = 0; i < nqqcwtendaa(); ++i) qqcwsub_tendaa[n][i][jsub] = qnumcw_delaa[n][i] / (fcvt_num() * deltat); - for (int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { qqcwsub4[iaer][jsub] = qaercw4[iaer][n] / fcvt_aer(iaer); - for (int i = 0; i < nqqcwtendaa(); ++i) + for(int i = 0; i < nqqcwtendaa(); ++i) qqcwsub_tendaa[iaer][i][jsub] = qaercw_delaa[iaer][n][i] / (fcvt_aer(iaer) * deltat); } @@ -1530,20 +1479,23 @@ void mam_amicphys_1gridcell( } } -} // anonymous namespace +} // anonymous namespace KOKKOS_INLINE_FUNCTION -void modal_aero_amicphys_intr( - const AmicPhysConfig& config, const int nstep, const Real deltat, const Real t, const Real pmid, const Real pdel, - const Real zm, const Real pblh, const Real qv, const Real cld, - Real q[gas_pcnst()], Real qqcw[gas_pcnst()], const Real q_pregaschem[gas_pcnst()], - const Real q_precldchem[gas_pcnst()], const Real qqcw_precldchem[gas_pcnst()], - Real q_tendbb[gas_pcnst()][nqtendbb()], Real qqcw_tendbb[gas_pcnst()][nqtendbb()], - Real dgncur_a[AeroConfig::num_modes()], - Real dgncur_awet[AeroConfig::num_modes()], - Real wetdens_host[AeroConfig::num_modes()], - Real qaerwat[AeroConfig::num_modes()]) { - +void modal_aero_amicphys_intr(const AmicPhysConfig &config, const int nstep, + const Real deltat, const Real temp, + const Real pmid, const Real pdel, const Real zm, + const Real pblh, const Real qv, const Real cld, + Real q[gas_pcnst()], Real qqcw[gas_pcnst()], + const Real q_pregaschem[gas_pcnst()], + const Real q_precldchem[gas_pcnst()], + const Real qqcw_precldchem[gas_pcnst()], + Real q_tendbb[gas_pcnst()][nqtendbb()], + Real qqcw_tendbb[gas_pcnst()][nqtendbb()], + Real dgncur_a[AeroConfig::num_modes()], + Real dgncur_awet[AeroConfig::num_modes()], + Real wetdens_host[AeroConfig::num_modes()], + Real qaerwat[AeroConfig::num_modes()]) { /* nstep ! model time-step number nqtendbb ! dimension for q_tendbb @@ -1616,8 +1568,8 @@ void modal_aero_amicphys_intr( // the processes are condensation/evaporation (and associated aging), // renaming, coagulation, and nucleation - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqtendbb(); ++j) + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqtendbb(); ++j) q_tendbb[i][j] = 0.0, qqcw_tendbb[i][j] = 0.0; // get saturation mixing ratio @@ -1625,7 +1577,7 @@ void modal_aero_amicphys_intr( // ev_sat(1:ncol,1:pver), qv_sat(1:ncol,1:pver) ) const Real epsqs = haero::Constants::weight_ratio_h2o_air; // Saturation vapor pressure - const Real ev_sat = conversions::vapor_saturation_pressure_magnus(t, pmid); + const Real ev_sat = conversions::vapor_saturation_pressure_magnus(temp, pmid); // Saturation specific humidity const Real qv_sat = epsqs * ev_sat / (pmid - (1 - epsqs) * ev_sat); @@ -1644,126 +1596,116 @@ void modal_aero_amicphys_intr( Real qqcwsub3[gas_pcnst()][maxsubarea()]; // aerosol water mixing ratios (mol/mol) Real qaerwatsub3[AeroConfig::num_modes()][maxsubarea()]; - construct_subareas_1gridcell(cld, relhumgcm, // in - q_pregaschem, q_precldchem, // in - qqcw_precldchem, // in - q, qqcw, // in - nsubarea, ncldy_subarea, jclea, jcldy, // out - iscldy_subarea, afracsub, relhumsub, // out - qsub1, qsub2, qsub3, // out - qqcwsub1, qqcwsub2, qqcwsub3, qaerwatsub3, // out - qaerwat // in + construct_subareas_1gridcell(cld, relhumgcm, // in + q_pregaschem, q_precldchem, // in + qqcw_precldchem, // in + q, qqcw, // in + nsubarea, ncldy_subarea, jclea, jcldy, // out + iscldy_subarea, afracsub, relhumsub, // out + qsub1, qsub2, qsub3, // out + qqcwsub1, qqcwsub2, qqcwsub3, + qaerwatsub3, // out + qaerwat // in ); // Initialize the "after-amicphys" values - Real qsub4[gas_pcnst()][maxsubarea()] = {}; - Real qqcwsub4[gas_pcnst()][maxsubarea()] = {}; + Real qsub4[gas_pcnst()][maxsubarea()] = {}; + Real qqcwsub4[gas_pcnst()][maxsubarea()] = {}; Real qaerwatsub4[AeroConfig::num_modes()][maxsubarea()] = {}; // // start integration // Real dgn_a[num_modes], dgn_awet[num_modes], wetdens[num_modes]; - for (int n = 0; n < num_modes; ++n) { - dgn_a[n] = dgncur_a[n]; + for(int n = 0; n < num_modes; ++n) { + dgn_a[n] = dgncur_a[n]; dgn_awet[n] = dgncur_awet[n]; - wetdens[n] = haero::max(1000.0, wetdens_host[n]); + wetdens[n] = haero::max(1000.0, wetdens_host[n]); } - Real qsub_tendaa[gas_pcnst()][nqtendaa()][maxsubarea()] = {}; + Real qsub_tendaa[gas_pcnst()][nqtendaa()][maxsubarea()] = {}; Real qqcwsub_tendaa[gas_pcnst()][nqqcwtendaa()][maxsubarea()] = {}; - mam_amicphys_1gridcell(config, nstep, deltat, - nsubarea, ncldy_subarea, iscldy_subarea, afracsub, t, - pmid, pdel, zm, pblh, relhumsub, dgn_a, dgn_awet, - wetdens, qsub1, qsub2, qqcwsub2, qsub3, qqcwsub3, - qaerwatsub3, qsub4, qqcwsub4, qaerwatsub4, qsub_tendaa, - qqcwsub_tendaa); + mam_amicphys_1gridcell(config, nstep, deltat, nsubarea, ncldy_subarea, + iscldy_subarea, afracsub, temp, pmid, pdel, zm, pblh, + relhumsub, dgn_a, dgn_awet, wetdens, qsub1, qsub2, + qqcwsub2, qsub3, qqcwsub3, qaerwatsub3, qsub4, + qqcwsub4, qaerwatsub4, qsub_tendaa, qqcwsub_tendaa); // // form new grid-mean mix-ratios Real qgcm4[gas_pcnst()]; Real qgcm_tendaa[gas_pcnst()][nqtendaa()]; Real qaerwatgcm4[num_modes]; - if (nsubarea == 1) { - for (int i = 0; i < gas_pcnst(); ++i) - qgcm4[i] = qsub4[i][0]; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqtendaa(); ++j) + if(nsubarea == 1) { + for(int i = 0; i < gas_pcnst(); ++i) qgcm4[i] = qsub4[i][0]; + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqtendaa(); ++j) qgcm_tendaa[i][j] = qsub_tendaa[i][j][0]; - for (int i = 0; i < num_modes; ++i) - qaerwatgcm4[i] = qaerwatsub4[i][0]; + for(int i = 0; i < num_modes; ++i) qaerwatgcm4[i] = qaerwatsub4[i][0]; } else { - for (int i = 0; i < gas_pcnst(); ++i) - qgcm4[i] = 0.0; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqtendaa(); ++j) - qgcm_tendaa[i][j] = 0.0; - for (int n = 0; n < nsubarea; ++n) { - for (int i = 0; i < gas_pcnst(); ++i) + for(int i = 0; i < gas_pcnst(); ++i) qgcm4[i] = 0.0; + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqtendaa(); ++j) qgcm_tendaa[i][j] = 0.0; + for(int n = 0; n < nsubarea; ++n) { + for(int i = 0; i < gas_pcnst(); ++i) qgcm4[i] += qsub4[i][n] * afracsub[n]; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqtendaa(); ++j) + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqtendaa(); ++j) qgcm_tendaa[i][j] = qgcm_tendaa[i][j] + qsub_tendaa[i][j][n] * afracsub[n]; } - for (int i = 0; i < num_modes; ++i) + for(int i = 0; i < num_modes; ++i) // for aerosol water use the clear sub-area value qaerwatgcm4[i] = qaerwatsub4[i][jclea - 1]; } Real qqcwgcm4[gas_pcnst()]; Real qqcwgcm_tendaa[gas_pcnst()][nqqcwtendaa()]; - if (ncldy_subarea <= 0) { - for (int i = 0; i < gas_pcnst(); ++i) - qqcwgcm4[i] = haero::max(0.0, qqcw[i]); - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqqcwtendaa(); ++j) - qqcwgcm_tendaa[i][j] = 0.0; - } else if (nsubarea == 1) { - for (int i = 0; i < gas_pcnst(); ++i) - qqcwgcm4[i] = qqcwsub4[i][0]; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqqcwtendaa(); ++j) + if(ncldy_subarea <= 0) { + for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = haero::max(0.0, qqcw[i]); + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] = 0.0; + } else if(nsubarea == 1) { + for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = qqcwsub4[i][0]; + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] = qqcwsub_tendaa[i][j][0]; } else { - for (int i = 0; i < gas_pcnst(); ++i) - qqcwgcm4[i] = 0.0; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqqcwtendaa(); ++j) - qqcwgcm_tendaa[i][j] = 0.0; - for (int n = 0; n < nsubarea; ++n) { - if (iscldy_subarea[n]) { - for (int i = 0; i < gas_pcnst(); ++i) + for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = 0.0; + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] = 0.0; + for(int n = 0; n < nsubarea; ++n) { + if(iscldy_subarea[n]) { + for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] += qqcwsub4[i][n] * afracsub[n]; - for (int i = 0; i < gas_pcnst(); ++i) - for (int j = 0; j < nqqcwtendaa(); ++j) + for(int i = 0; i < gas_pcnst(); ++i) + for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] += qqcwsub_tendaa[i][j][n] * afracsub[n]; } } } - for (int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if (lmapcc_all(lmz) > 0) { + for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { + if(lmapcc_all(lmz) > 0) { // HW, to ensure non-negative q[lmz] = haero::max(qgcm4[lmz], 0.0); - if (lmapcc_all(lmz) >= lmapcc_val_aer()) { + if(lmapcc_all(lmz) >= lmapcc_val_aer()) { // HW, to ensure non-negative qqcw[lmz] = haero::max(qqcwgcm4[lmz], 0.0); } } } - for (int i = 0; i < gas_pcnst(); ++i) { - if (iqtend_cond() < nqtendbb()) + for(int i = 0; i < gas_pcnst(); ++i) { + if(iqtend_cond() < nqtendbb()) q_tendbb[i][iqtend_cond()] = qgcm_tendaa[i][iqtend_cond()]; - if (iqtend_rnam() < nqtendbb()) + if(iqtend_rnam() < nqtendbb()) q_tendbb[i][iqtend_rnam()] = qgcm_tendaa[i][iqtend_rnam()]; - if (iqtend_nnuc() < nqtendbb()) + if(iqtend_nnuc() < nqtendbb()) q_tendbb[i][iqtend_nnuc()] = qgcm_tendaa[i][iqtend_nnuc()]; - if (iqtend_coag() < nqtendbb()) + if(iqtend_coag() < nqtendbb()) q_tendbb[i][iqtend_coag()] = qgcm_tendaa[i][iqtend_coag()]; - if (iqqcwtend_rnam() < nqqcwtendbb()) + if(iqqcwtend_rnam() < nqqcwtendbb()) qqcw_tendbb[i][iqqcwtend_rnam()] = qqcwgcm_tendaa[i][iqqcwtend_rnam()]; } - for (int i = 0; i < num_modes; ++i) - qaerwat[i] = qaerwatgcm4[i]; + for(int i = 0; i < num_modes; ++i) qaerwat[i] = qaerwatgcm4[i]; } -} // namespace scream::impl +} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/mam_coupling.hpp b/components/eamxx/src/physics/mam/mam_coupling.hpp index a3a5f746aa3..4b4bb0043cf 100644 --- a/components/eamxx/src/physics/mam/mam_coupling.hpp +++ b/components/eamxx/src/physics/mam/mam_coupling.hpp @@ -647,7 +647,7 @@ void compute_vertical_layer_heights(const Team& team, const auto p_mid = ekat::subview(dry_atm.p_mid, column_index); const auto T_mid = ekat::subview(dry_atm.T_mid, column_index); const auto pseudo_density = ekat::subview(dry_atm.p_del, column_index); - + // NOTE: we are using dry qv. Does calculate_dz require dry or wet? PF::calculate_dz(team, pseudo_density, p_mid, T_mid, qv, // inputs dz);//output @@ -902,6 +902,27 @@ void transfer_prognostics_to_work_arrays(const mam4::Prognostics &progs, } } +// FIXME: check if we have ported these function in mam4xx. If no, let's move there. +KOKKOS_INLINE_FUNCTION +void mmr2vmr(const Real q[gas_pcnst()],//in + const Real mw[gas_pcnst()],//in + Real vmr[gas_pcnst()])// out +{ +for (int i = 0; i < gas_pcnst(); ++i) { + vmr[i] = mam4::conversions::vmr_from_mmr(q[i], mw[i]); +} +}//mmr2vmr +// FIXME: check if we have ported these function in mam4xx. If no, let's move there. +KOKKOS_INLINE_FUNCTION +void vmr2mmr(const Real vmr[gas_pcnst()], //in + const Real mw[gas_pcnst()], // in + Real q[gas_pcnst()]) // out +{ +for (int i = 0; i < gas_pcnst(); ++i) { + q[i] = mam4::conversions::mmr_from_vmr(vmr[i], mw[i]); +} +}//mmr2vmr + // converts the quantities in the work arrays q and qqcw from mass/number // mixing ratios to volume/number mixing ratios KOKKOS_INLINE_FUNCTION From 86a5db98c81d06a4df3f92760c4b621d350845d6 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Thu, 22 Aug 2024 14:52:47 -0600 Subject: [PATCH 16/31] Coupling external forcing. Fixes files and paths for the microphysics cime and standalone tests Changes file paths to match new directory structure, changes file names to add current date strings Get nc files for single-process tests. Chaning vertical interpolation function. The Fortran version of MAM4 uses a cyclical method to read data from NC files. In this approach, the user provides a year, and the code locates that year in the dataset and repeats the data in yearly cycles. Here, we follow the SPA reader's convention, assuming that the data is saved monthly and that each cycle spans one year. Fix species order in array of constant invariants. Use a list of species names to ensure that the order of species in vertical emission is correct. --- .../cime_config/namelist_defaults_scream.xml | 16 +- ...mxx_mam_microphysics_process_interface.cpp | 198 ++++++++++-------- ...mxx_mam_microphysics_process_interface.hpp | 6 +- .../physics/mam/impl/gas_phase_chemistry.cpp | 2 +- .../src/physics/mam/impl/helper_micro.hpp | 113 ++++++++-- .../mam/aero_microphys/CMakeLists.txt | 24 ++- .../mam/aero_microphys/input.yaml | 22 +- 7 files changed, 253 insertions(+), 128 deletions(-) diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 9d9d83201c3..2882032ae75 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -271,14 +271,14 @@ be lost if SCREAM_HACK_XML is not enabled. - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc - 20100101 - type="file" doc=""> ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/RSF_GT200nm_v3.0_c080811.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/temp_prs_GT200nm_JPL10_c130206.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/physprops/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne4pg2/oxid_ne4pg2_L26_1850-2015_c20240821.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + 20100101 + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_bc_a4_elev_ne4pg2_2010_clim_c20240821.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so2_elev_ne4pg2_2010_clim_c20240821.nc diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 2b7f29da991..d3e48ed7e89 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -97,7 +97,6 @@ void MAMMicrophysics::set_defaults_() { void MAMMicrophysics::configure(const ekat::ParameterList ¶ms) { set_defaults_(); - // FIXME: implement "namelist" parsing } void MAMMicrophysics::set_grids( @@ -231,11 +230,19 @@ void MAMMicrophysics::set_grids( } linoz_data_beg_.set_file_type(tracer_file_type); linoz_data_end_.set_file_type(tracer_file_type); + // FIXME: get this from input.yaml + int cyclical_ymd=20100100; //in format YYYYMMDD + std::vector linoz_dates; + int cyclical_ymd_index=-1; + scream::mam_coupling::get_time_from_ncfile(linoz_file_name_, cyclical_ymd, cyclical_ymd_index, linoz_dates); + trace_time_state_.offset_time_index=cyclical_ymd_index; + linoz_time_state_.offset_time_index=cyclical_ymd_index; } { oxid_file_name_ = m_params.get("mam4_oxid_file_name"); std::string spa_map_file = ""; - std::vector var_names{"O3", "HO2", "NO3", "OH"}; + //NOTE: order matches mam4xx: + std::vector var_names{"O3", "OH", "NO3", "HO2"}; TracerFileType tracer_file_type; TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( grid_, oxid_file_name_, spa_map_file, var_names, tracer_file_type); @@ -247,6 +254,12 @@ void MAMMicrophysics::set_grids( } tracer_data_beg_.set_file_type(tracer_file_type); tracer_data_end_.set_file_type(tracer_file_type); + // FIXME: get this from input.yaml + int cyclical_ymd=20150101; //in format YYYYMMDD + std::vector oxi_dates; + int cyclical_ymd_index=-1; + scream::mam_coupling::get_time_from_ncfile(oxid_file_name_, cyclical_ymd, cyclical_ymd_index, oxi_dates); + trace_time_state_.offset_time_index=cyclical_ymd_index; } { @@ -255,63 +268,32 @@ void MAMMicrophysics::set_grids( // NOTE: order of forcing species is important. // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 ','pom_a4 ','bc_a4 ', // 'num_a1 ','num_a2 ','num_a4 ','SOAG ' } - - std::string mam4_so2_verti_emiss_file_name = - //"cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc" - m_params.get("mam4_so2_verti_emiss_file_name"); + // This order corresponds to files in namelist e3smv2 + // Note that I change this order to match extfrc_lst + // 1,9,2,6,3,7,4,5,8 + extfrc_lst_=std::vector({"so2","so4_a1","so4_a2","pom_a4","bc_a4", + "num_a1","num_a2","num_a4","soag"}); + + for (const auto& var_name : extfrc_lst_) { + std::string item_name= "mam4_"+var_name+"_verti_emiss_file_name"; + const auto file_name = m_params.get(item_name); + vert_emis_file_name_[var_name] = file_name; + } vert_emis_var_names_["so2"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; - vert_emis_file_name_["so2"] = mam4_so2_verti_emiss_file_name; - - // so4_a1 - std::string mam4_so4_a1_verti_emiss_file_name=base_path+"cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["so4_a1"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; - vert_emis_file_name_["so4_a1"] = mam4_so4_a1_verti_emiss_file_name; - - // so4_a2 - std::string mam4_so4_a2_verti_emiss_file_name=base_path+"cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["so4_a2"] = { "contvolc"}; - vert_emis_file_name_["so4_a2"] = mam4_so4_a2_verti_emiss_file_name; - - // pom_a4 - std::string mam4_pom_a4_verti_emiss_file_name=base_path+"cmip6_mam4_pom_a4_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["pom_a4"] = {"BB"}; - vert_emis_file_name_["pom_a4"] = mam4_pom_a4_verti_emiss_file_name; - - // cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc - std::string mam4_bc_a4_verti_emiss_file_name = - m_params.get("mam4_bc_a4_verti_emiss_file_name"); - vert_emis_file_name_["bc_a4"] = mam4_bc_a4_verti_emiss_file_name; vert_emis_var_names_["bc_a4"] = {"BB"}; - - // num_a1 - std::string mam4_num_a1_verti_emiss_file_name=base_path+"cmip6_mam4_num_a1_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["num_a1"] = {"num_a1_SO4_ELEV_BB","num_a1_SO4_ELEV_ENE", "num_a1_SO4_ELEV_IND", "num_a1_SO4_ELEV_contvolc"}; - vert_emis_file_name_["num_a1"] = mam4_num_a1_verti_emiss_file_name; - - // num_a2 - std::string mam4_num_a2_verti_emiss_file_name=base_path+"cmip6_mam4_num_a2_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["num_a2"] = {"num_a2_SO4_ELEV_contvolc"}; - vert_emis_file_name_["num_a2"] = mam4_num_a2_verti_emiss_file_name; - // num_a4 - // FIXME: why the sectors in this files are num_a1; I guess this should be num_a4? Is this a bug in the orginal nc files? - // QUESTION... - std::string mam4_num_a4_verti_emiss_file_name=base_path+"cmip6_mam4_num_a4_elev_ne2np4_2010_clim_c20190821_OD.nc"; + // FIXME: why the sectors in this files are num_a1; + // I guess this should be num_a4? Is this a bug in the orginal nc files? vert_emis_var_names_["num_a4"] = {"num_a1_BC_ELEV_BB", "num_a1_POM_ELEV_BB"}; - vert_emis_file_name_["num_a4"] = mam4_num_a4_verti_emiss_file_name; - - //SOAG - // m_params.get("mam4_soag_verti_emiss_file_name"); - // FIXME: get this file from namelist - std::string base_path="/ascldap/users/odiazib/Documents/Oscar/CODE/eagles-project/scream_micro/ver_emis/"; - std::string mam4_soag_verti_emiss_file_name=base_path+"cmip6_mam4_soag_elev_ne2np4_2010_clim_c20190821_OD.nc"; vert_emis_var_names_["soag"] = {"SOAbb_src","SOAbg_src", "SOAff_src"}; - vert_emis_file_name_["soag"] = mam4_soag_verti_emiss_file_name; - - for (const auto& item : vert_emis_file_name_) { - const auto var_name = item.first; - const auto file_name = item.second; + for (const auto& var_name : extfrc_lst_) { + const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; TracerFileType tracer_file_type; @@ -329,6 +311,16 @@ void MAMMicrophysics::set_grids( vert_emis_data_out_.push_back(data_out); vert_emis_data_beg_.push_back(data_beg); vert_emis_data_end_.push_back(data_end); + }// var_name vert emissions + + { + // NOTE: Here I am assuming all vert file have same times. + // FIXME: get this from input.yaml + int cyclical_ymd=20100101; //in format YYYYMMDD + std::vector vertical_emiss_dates; + int cyclical_ymd_index=-1; + scream::mam_coupling::get_time_from_ncfile(vert_emis_file_name_["num_a4"], cyclical_ymd, cyclical_ymd_index, vertical_emiss_dates); + vert_emiss_time_state_.offset_time_index=cyclical_ymd_index; } } } @@ -562,6 +554,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { tracer_data_out_.init(num_cols_io, num_levs_io, nvars); tracer_data_out_.allocate_data_views(); tracer_data_out_.allocate_ps(); + tracer_data_out_.allocate_work_vert_inter(); p_src_invariant_ = view_2d("pressure_src_invariant", num_cols_io, num_levs_io); @@ -593,6 +586,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { linoz_data_out_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_out_.allocate_data_views(); + linoz_data_out_.allocate_work_vert_inter(); if(linoz_data_out_.file_type == TracerFileType::FORMULA_PS) { linoz_data_out_.allocate_ps(); } else if(linoz_data_out_.file_type == TracerFileType::ZONAL) { @@ -610,12 +604,17 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { int i=0; int offset_emis_ver=0; - for (const auto& item : vert_emis_file_name_) { - const auto var_name = item.first; - const auto file_name = item.second; + for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + const auto var_name = *it; + const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; const int nvars = int(var_names.size()); + forcings_[i].nsectors = nvars; + // I am assuming the order of species in the above code. + // Indexing in mam4xx is fortran. + forcings_[i].frc_ndx = i+1; + // We may need to move this line where we read files. const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); const int num_cols_io_emis = io_grid_emis->get_num_local_dofs(); // Number of columns on this rank @@ -637,25 +636,31 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { vert_emis_data_out_[i].allocate_data_views(); if(vert_emis_data_out_[i].file_type == TracerFileType::FORMULA_PS) { vert_emis_data_out_[i].allocate_ps(); + forcings_[i].file_alt_data=false; } else if(vert_emis_data_out_[i].file_type == TracerFileType::VERT_EMISSION) { auto zi_src = scream::mam_coupling::get_altitude_int( VertEmissionsHorizInterp_[i], file_name); vert_emis_altitude_int_.push_back(zi_src); + + forcings_[i].file_alt_data=true; } for (int isp = 0; isp < nvars; ++isp) { EKAT_REQUIRE_MSG( - mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS <= int(offset_emis_ver), + offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), "Error! Number of fields is bigger than MAX_NUM_VERT_EMISSION_FIELDS. Increase the MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); - + forcings_[i].offset=offset_emis_ver; vert_emis_output_[isp+offset_emis_ver] = - view_2d("vert_emis_output_", num_cols_io_emis, num_levs_io_emis); + view_2d("vert_emis_output_", ncol_, nlev_); } - i++; offset_emis_ver+=nvars; } // end i + + constexpr int extcnt = + mam4::gas_chemistry::extcnt; + extfrc_=view_3d("extfrc_", ncol_, nlev_, extcnt); } invariants_ = view_3d("invarians", ncol_, nlev_, mam4::gas_chemistry::nfs); @@ -716,11 +721,11 @@ void MAMMicrophysics::run_impl(const double dt) { // /* Update the TracerTimeState to reflect the current time, note the // addition of dt */ - linoz_time_state_.t_now = ts.frac_of_year_in_days(); + trace_time_state_.t_now = ts.frac_of_year_in_days(); // FIXME: we do not need altitude_int for invariant tracers and linoz fields. view_1d dummy_altitude_int; scream::mam_coupling::advance_tracer_data( - TracerDataReader_, *TracerHorizInterp_, ts, linoz_time_state_, + TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, tracer_data_beg_, tracer_data_end_, tracer_data_out_, p_src_invariant_, dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, cnst_offline_); @@ -729,25 +734,23 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_data_beg_, linoz_data_end_, linoz_data_out_, p_src_linoz_, dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, linoz_output); + vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); int i=0; - int offset_emis_ver=0; - for (const auto& item : vert_emis_file_name_) { - const auto var_name = item.first; - const auto file_name = item.second; + for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + const auto var_name = *it; + const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; - const int nvars = int(var_names.size()); - view_2d vert_emis_output[nvars]; - for (int isp = 0; isp < nvars; ++isp) + const int nsectors = int(var_names.size()); + view_2d vert_emis_output[nsectors]; + for (int isp = 0; isp < nsectors; ++isp) { - vert_emis_output[isp]= vert_emis_output_[isp+offset_emis_ver]; + vert_emis_output[isp]= vert_emis_output_[isp+forcings_[i].offset]; } - offset_emis_ver+=nvars; scream::mam_coupling::advance_tracer_data( VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, - linoz_time_state_, vert_emis_data_beg_[i], vert_emis_data_end_[i], + vert_emiss_time_state_, vert_emis_data_beg_[i], vert_emis_data_end_[i], vert_emis_data_out_[i], p_src_linoz_, dry_atm_.p_mid, vert_emis_altitude_int_[i], dry_atm_.z_iface, vert_emis_output); - i++; } const_view_1d &col_latitudes = col_latitudes_; const_view_1d &col_longitudes = col_longitudes_; @@ -759,9 +762,6 @@ void MAMMicrophysics::run_impl(const double dt) { const int nlev = nlev_; const Config &config = config_; const auto &step = step_; - // FIXME: read relevant linoz climatology data from file(s) based on time - - // FIXME: read relevant chlorine loading data from file based on time const auto &work_photo_table = work_photo_table_; const auto &photo_rates = photo_rates_; @@ -797,16 +797,15 @@ void MAMMicrophysics::run_impl(const double dt) { constexpr int gas_pcnst = mam_coupling::gas_pcnst(); constexpr int nqtendbb = mam_coupling::nqtendbb(); - // FIXME: I believe Balwinder add this array somewhere in mam4xx or eamxx. - const Real adv_mass[gas_pcnst] = { - 47.998200, 34.013600, 98.078400, 64.064800, 62.132400, - 12.011000, 115.107340, 12.011000, 12.011000, 12.011000, - 135.064039, 58.442468, 250092.672000, 1.007400, 115.107340, - 12.011000, 58.442468, 250092.672000, 1.007400, 135.064039, - 58.442468, 115.107340, 12.011000, 12.011000, 12.011000, - 250092.672000, 1.007400, 12.011000, 12.011000, 250092.672000, - 1.007400}; + constexpr auto adv_mass = mam4::gas_chemistry::adv_mass; constexpr int pcnst = mam4::pcnst; + const auto vert_emis_output = vert_emis_output_; + const auto extfrc = extfrc_; + const auto forcings = forcings_; + constexpr int extcnt = mam4::gas_chemistry::extcnt; + + // FIXME: remove this hard-code value + const int offset_aerosol = mam4::utils::gasses_start_ind(); // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for( @@ -835,9 +834,27 @@ void MAMMicrophysics::run_impl(const double dt) { // set up diagnostics mam4::Diagnostics diags(nlev); + const auto invariants_icol = ekat::subview(invariants, icol); + mam4::mo_setext::Forcing forcings_in[extcnt]; + for (int i = 0; i < extcnt; ++i) + { + int nsectors = forcings[i].nsectors; + int frc_ndx = forcings[i].frc_ndx; + auto file_alt_data= forcings[i].file_alt_data; + + forcings_in[i].nsectors=nsectors; + forcings_in[i].frc_ndx=frc_ndx; + // We may need to move this line where we read files. + forcings_in[i].file_alt_data=file_alt_data; + for (int isec = 0; isec < forcings[i].nsectors; ++isec) + { + const auto field = vert_emis_output[isec+forcings[i].offset]; + forcings_in[i].fields_data[isec]=ekat::subview(field, icol); + } + } + const auto extfrc_icol = ekat::subview(extfrc, icol); - // - auto invariants_icol = ekat::subview(invariants, icol); + mam4::mo_setext::extfrc_set(forcings_in, extfrc_icol); view_1d cnst_offline_icol[mam4::mo_setinv::num_tracer_cnst]; for (int i = 0; i < mam4::mo_setinv::num_tracer_cnst; ++i) { @@ -916,8 +933,7 @@ void MAMMicrophysics::run_impl(const double dt) { // } mam4::utils::extract_qqcw_from_prognostics(progs,qqcw_long,k); - // FIXME: remove this hard-code value - const int offset_aerosol = 9; + for (int i = offset_aerosol; i < pcnst; ++i) { q[i-offset_aerosol] =state_q[i]; qqcw[i-offset_aerosol] =qqcw_long[i]; @@ -954,11 +970,11 @@ void MAMMicrophysics::run_impl(const double dt) { photo_rates_k[i] = photo_rates_icol(k, i); } - Real extfrc_k[mam4::gas_chemistry::extcnt]; - for (int i = 0; i < mam4::gas_chemistry::extcnt; ++i) { - //extfrc_k[i] = extfrc_icol(k, i); - extfrc_k[i] = 0.0; - } + // Real extfrc_k[mam4::gas_chemistry::extcnt]; + // for (int i = 0; i < mam4::gas_chemistry::extcnt; ++i) { + // extfrc_k[i] = extfrc_icol(k, i); + // } + const auto& extfrc_k = ekat::subview(extfrc_icol,k); Real invariants_k[mam4::gas_chemistry::nfs]; for(int i = 0; i < mam4::gas_chemistry::nfs; ++i) { @@ -966,7 +982,7 @@ void MAMMicrophysics::run_impl(const double dt) { } impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k, extfrc_k, invariants_k, vmr); + photo_rates_k, extfrc_k.data(), invariants_k, vmr); //---------------------- // Aerosol microphysics @@ -1049,7 +1065,7 @@ void MAMMicrophysics::run_impl(const double dt) { state_q[i] = q[i-offset_aerosol]; qqcw_long[i] = qqcw[i-offset_aerosol]; } - // mam4::utils::inject_stateq_to_prognostics(state_q,progs,k); + mam4::utils::inject_stateq_to_prognostics(state_q,progs,k); // std::cout << "state_q: "; // for (int i = 0; i < pcnst; ++i) { // std::cout << state_q[i] << " "; diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 97a63b00e97..5e758936595 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -253,6 +253,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_3d photo_rates_; // invariants members + mam_coupling::TracerTimeState trace_time_state_; std::shared_ptr TracerDataReader_; std::shared_ptr TracerHorizInterp_; mam_coupling::TracerData tracer_data_end_; @@ -273,8 +274,10 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::string linoz_file_name_; // Vertical emission uses 9 files, here I am using std::vector to stote instance of each file. + mam_coupling::TracerTimeState vert_emiss_time_state_; std::vector> VertEmissionsDataReader_; std::vector> VertEmissionsHorizInterp_; + std::vector extfrc_lst_; std::vector vert_emis_data_end_; std::vector vert_emis_data_beg_; std::vector vert_emis_data_out_; @@ -282,7 +285,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::map< std::string, std::string >vert_emis_file_name_; std::map< std::string, std::vector > vert_emis_var_names_; view_2d vert_emis_output_[mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS]; - + view_3d extfrc_; + mam_coupling::ForcingHelper forcings_[mam4::gas_chemistry::extcnt]; }; // MAMMicrophysics diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index f7828cf8470..d3d77119d3d 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -496,7 +496,7 @@ void gas_phase_chemistry( } // ... Form the washout rates - Real het_rates[gas_pcnst]; + Real het_rates[gas_pcnst]={0}; // FIXME: not ported yet // sethet(het_rates, pmid, zmid, phis, temp, cmfdqr, prain, nevapr, delt, // invariants[indexm], q); diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 1ac8096f79a..a4bebe03278 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -21,6 +21,22 @@ using ESU = ekat::ExeSpaceUtils; using C = scream::physics::Constants; using LIV = ekat::LinInterp; +// We have a similar version in MAM4xx. +// This version was created because the data view cannot be modified +// inside the parallel_for. +// This struct will be used in init while reading nc files. +// The MAM4xx version will be used instead of parallel_for that loops over cols. +struct ForcingHelper { + // This index is in Fortran format. i.e. starts in 1 + int frc_ndx; + // does this file have altitude? + bool file_alt_data; + // number of sectors per forcing + int nsectors; + // offset in output vector from reader + int offset; + }; + enum TracerFileType { // file with PS ncol, lev, and time FORMULA_PS, @@ -50,6 +66,8 @@ struct TracerTimeState { Real t_now; // Number of days in the current month, cast as a Real Real days_this_month; + // + int offset_time_index{0}; }; // TricerTimeState struct TracerData { @@ -75,7 +93,7 @@ struct TracerData { view_1d ps; const_view_1d hyam; const_view_1d hybm; - + view_int_1d work_vert_inter[MAX_NVARS_TRACER];; TracerFileType file_type; void allocate_data_views() { @@ -134,6 +152,19 @@ struct TracerData { hyam = hyam_f.get_view(); hybm = hyam_f.get_view(); } + + void allocate_work_vert_inter() + { + if(file_type == FORMULA_PS || + file_type == ZONAL) { + // we only need work array for FORMULA_PS or ZONAL + for(int ivar = 0; ivar < nvars_; ++ivar) { + work_vert_inter[ivar] = + view_int_1d("allocate_work_vertical_interpolation", ncol_); + } + } + + } }; inline const_view_1d get_altitude_int( @@ -246,6 +277,36 @@ inline void create_linoz_chlorine_reader( scorpio::release_file(linoz_chlorine_file); } +// Gets the times from the NC file +// Given a date in the format YYYYMMDD, returns its index in the time dimension. +inline void get_time_from_ncfile( + const std::string &file_name, + const int cyclical_ymd, // in format YYYYMMDD + int & cyclical_ymd_index, + std::vector &dates) { + // in file_name: name of the NC file + // in cyclical_ymd: date in the format YYYYMMDD + // out cyclical_ymd_index: time index for cyclical_ymd + // out dates: date in YYYYMMDD format + scorpio::register_file(file_name, scorpio::Read); + const int nlevs_time = scorpio::get_time_len(file_name); + cyclical_ymd_index=-1; + for(int itime = 0; itime < nlevs_time; ++itime) { + int date; + scorpio::read_var(file_name, "date", &date, itime); + // std::cout << itime << " date: " << date << "\n"; + if(date >= cyclical_ymd && cyclical_ymd_index ==-1) { + cyclical_ymd_index=itime; + } + dates.push_back(date); + } // end itime + scorpio::release_file(file_name); + EKAT_REQUIRE_MSG(cyclical_ymd_index>=0, + "Error! Current model time ("+std::to_string(cyclical_ymd)+") is not within "+ + "Tracer time period: ["+std::to_string(dates[0])+", "+ + "("+std::to_string(dates[nlevs_time-1])+").\n"); +} + inline Real chlorine_loading_advance(const util::TimeStamp &ts, std::vector &values, std::vector &time_secs) { @@ -339,21 +400,6 @@ inline std::shared_ptr create_horiz_remapper( const auto layout_2d = tgt_grid->get_2d_scalar_layout(); const auto layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); - // FieldLayout layout_3d_mid; - // if ( has_altitude ) { - - // auto make_layout = [](const std::vector& extents, - // const std::vector& names) - // { - // std::vector tags(extents.size(),CMP); - // return FieldLayout(tags,extents,names); - // }; - // layout_3d_mid = make_layout({ncols_model, nlevs_data}, - // {"ncol","altitude"}); - // // FieldLayout({FieldTag::Column,CMP},{ncols_model,nlevs_data}); - // } else { - // layout_3d_mid = tgt_grid->get_3d_scalar_layout(true); - // } const auto nondim = ekat::units::Units::nondimensional(); @@ -442,7 +488,7 @@ inline void update_tracer_timestate( // values // to be assigned. A timestep greater than a month is very unlikely // so we will proceed. - int next_month = (time_state.current_month + 1) % 12; + int next_month = time_state.offset_time_index + (time_state.current_month + 1) % 12; update_tracer_data_from_file(scorpio_reader, ts, next_month, tracer_horiz_interp, data_tracer_end); } @@ -579,6 +625,37 @@ inline void compute_p_src_zonal_files(const std::string &tracer_file_name, Kokkos::fence(); } +inline void perform_vertical_interpolation(const view_2d &p_src_c, + const const_view_2d &p_tgt_c, + const TracerData &input, + const view_2d output[]) + { + // At this stage, begin/end must have the same horiz dimensions + EKAT_REQUIRE(input.ncol_ == output[0].extent(0)); + const int ncol = input.ncol_; + const int levsiz =input.nlev_; + const int pver = mam4::nlev; + + const int num_vars = input.nvars_; + const auto work = input.work_vert_inter; + EKAT_REQUIRE(work[num_vars-1].data() != 0); + // vert_interp is serial in col and lev. + const auto policy_setup = ESU::get_default_team_policy(num_vars, 1); + + Kokkos::parallel_for( + "vert_interp", policy_setup, + KOKKOS_LAMBDA(typename LIV::MemberType const &team) + { + const int ivar = team.league_rank(); + vert_interp(ncol, levsiz, pver, p_src_c, + p_tgt_c, input.data[ivar], + output[ivar], + // work array + work[ivar]); + }); + } + +#if 0 inline void perform_vertical_interpolation(const view_2d &p_src_c, const const_view_2d &p_tgt_c, const TracerData &input, @@ -644,7 +721,7 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, }); Kokkos::fence(); } - +#endif // rebin is a port from: // https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 inline void rebin(int nsrc, int ntrg, const const_view_1d &src_x, diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt index 323d932484b..700f8fffc0f 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt +++ b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt @@ -23,7 +23,27 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/output.yaml ${CMAKE_CURRENT_BINARY_DIR}/output.yaml) # Ensure test input files are present in the data dir -GetInputFile(scream/init/${EAMxx_tests_IC_FILE_MAM4xx_72lev}) +set (TEST_INPUT_FILES + scream/init/${EAMxx_tests_IC_FILE_MAM4xx_72lev} + scream/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc + scream/mam4xx/invariants/ne2np4/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc + scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc + scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_pom_a4_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_num_a1_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_num_a2_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_num_a4_elev_ne2np4_2010_clim_c20240823.nc + scream/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_soag_elev_ne2np4_2010_clim_c20240823.nc +) +foreach (file IN ITEMS ${TEST_INPUT_FILES}) + GetInputFile(${file}) +endforeach() + # Compare output files produced by npX tests, to ensure they are bfb include (CompareNCFiles) @@ -40,5 +60,5 @@ if (SCREAM_ENABLE_BASELINE_TESTS) # Compare one of the output files with the baselines. # Note: one is enough, since we already check that np1 is BFB with npX set (OUT_FILE ${TEST_BASE_NAME}_output.INSTANT.nsteps_x1.np${TEST_RANK_END}.${RUN_T0}.nc) - CreateBaselineTest(${TEST_BASE_NAME} ${TEST_RANK_END} ${OUT_FILE} ${FIXTURES_BASE_NAME}) + CreateBaselineTest(${TEST_BASE_NAME} ${TEST_RANK_END} ${OUT_FILE} ${FIXTURES_BASE_NAME}) endif() diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 13354686829..32d477f3fda 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -11,14 +11,22 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] mam4_aero_microphys: - mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc - mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc - mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc + mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc + mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc mam4_chlorine_loading_ymd : 20100101 - mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/RSF_GT200nm_v3.0_c080811.nc - mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/physprops/temp_prs_GT200nm_JPL10_c130206.nc - mam4_bc_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240726_OD.nc - mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/physprops/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726_OD.nc + mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc + mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726.nc + mam4_so4_a1_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20240823.nc + mam4_so4_a2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20240823.nc + mam4_pom_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_pom_a4_elev_ne2np4_2010_clim_c20240823.nc + mam4_bc_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240823.nc + mam4_num_a1_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a1_elev_ne2np4_2010_clim_c20240823.nc + mam4_num_a2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a2_elev_ne2np4_2010_clim_c20240823.nc + mam4_num_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a4_elev_ne2np4_2010_clim_c20240823.nc + mam4_soag_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_soag_elev_ne2np4_2010_clim_c20240823.nc + grids_manager: Type: Mesh Free geo_data_source: IC_FILE From 355bf13e7b10449443a007888a2e89fba706f3b3 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 30 Aug 2024 10:32:03 -0600 Subject: [PATCH 17/31] 1. Use the Scorpio interface to get altitude from vertical emission files. I was getting zeros or junk values when I employed the AtmInput class. 2. Add altitude_int as a member of the Altitude class. Use .data() to avoid copying data. Updates to compute_water_content and remove it from interface. --- ...mxx_mam_microphysics_process_interface.cpp | 111 +++++++----------- ...mxx_mam_microphysics_process_interface.hpp | 1 - .../mam/impl/compute_water_content.cpp | 104 ++++++++++------ .../src/physics/mam/impl/helper_micro.hpp | 52 ++++---- .../mam/aero_microphys/input.yaml | 2 +- 5 files changed, 141 insertions(+), 129 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index d3e48ed7e89..79ab694a982 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -314,7 +314,7 @@ void MAMMicrophysics::set_grids( }// var_name vert emissions { - // NOTE: Here I am assuming all vert file have same times. + // NOTE: Here I am assuming all vert file have same times. // FIXME: get this from input.yaml int cyclical_ymd=20100101; //in format YYYYMMDD std::vector vertical_emiss_dates; @@ -505,11 +505,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] - // Load the first month into spa_end. - // Note: At the first time step, the data will be moved into spa_beg, - // and spa_end will be reloaded from file with the new month. - const int curr_month = timestamp().get_month() - 1; // 0-based - // const std::string linoz_chlorine_file = // "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; auto ts = // timestamp(); int chlorine_loading_ymd=20100101; @@ -544,7 +539,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { tracer_data_end_.init(num_cols_io, num_levs_io, nvars); scream::mam_coupling::update_tracer_data_from_file( - TracerDataReader_, timestamp(), curr_month, *TracerHorizInterp_, + TracerDataReader_, curr_month, *TracerHorizInterp_, tracer_data_end_); tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); @@ -575,7 +570,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { const int nvars = 8; linoz_data_end_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); scream::mam_coupling::update_tracer_data_from_file( - LinozDataReader_, timestamp(), curr_month, *LinozHorizInterp_, + LinozDataReader_, curr_month, *LinozHorizInterp_, linoz_data_end_); linoz_data_beg_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); @@ -611,10 +606,9 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { const int nvars = int(var_names.size()); forcings_[i].nsectors = nvars; - // I am assuming the order of species in the above code. + // I am assuming the order of species in extfrc_lst_. // Indexing in mam4xx is fortran. forcings_[i].frc_ndx = i+1; - // We may need to move this line where we read files. const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); const int num_cols_io_emis = io_grid_emis->get_num_local_dofs(); // Number of columns on this rank @@ -623,7 +617,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { ->get_num_vertical_levels(); // Number of levels per column vert_emis_data_end_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); scream::mam_coupling::update_tracer_data_from_file( - VertEmissionsDataReader_[i], timestamp(), curr_month, + VertEmissionsDataReader_[i], curr_month, *VertEmissionsHorizInterp_[i], vert_emis_data_end_[i]); vert_emis_data_beg_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); @@ -639,11 +633,10 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { forcings_[i].file_alt_data=false; } else if(vert_emis_data_out_[i].file_type == TracerFileType::VERT_EMISSION) { - auto zi_src = scream::mam_coupling::get_altitude_int( - VertEmissionsHorizInterp_[i], file_name); - vert_emis_altitude_int_.push_back(zi_src); - - forcings_[i].file_alt_data=true; + forcings_[i].file_alt_data=true; + // FIXME: Do not open this file three times + // I am getting zeros for altitude_int if I use AtmosphereInput + scream::mam_coupling::get_altitude_int(file_name, vert_emis_data_end_[i].altitude_int); } for (int isp = 0; isp < nvars; ++isp) @@ -722,17 +715,15 @@ void MAMMicrophysics::run_impl(const double dt) { // /* Update the TracerTimeState to reflect the current time, note the // addition of dt */ trace_time_state_.t_now = ts.frac_of_year_in_days(); - // FIXME: we do not need altitude_int for invariant tracers and linoz fields. - view_1d dummy_altitude_int; scream::mam_coupling::advance_tracer_data( TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, tracer_data_beg_, tracer_data_end_, tracer_data_out_, p_src_invariant_, - dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, cnst_offline_); + dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); scream::mam_coupling::advance_tracer_data( LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, linoz_data_beg_, linoz_data_end_, linoz_data_out_, p_src_linoz_, - dry_atm_.p_mid, dummy_altitude_int, dry_atm_.z_iface, linoz_output); + dry_atm_.p_mid, dry_atm_.z_iface, linoz_output); vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); int i=0; @@ -750,7 +741,7 @@ void MAMMicrophysics::run_impl(const double dt) { VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, vert_emiss_time_state_, vert_emis_data_beg_[i], vert_emis_data_end_[i], vert_emis_data_out_[i], p_src_linoz_, dry_atm_.p_mid, - vert_emis_altitude_int_[i], dry_atm_.z_iface, vert_emis_output); + dry_atm_.z_iface, vert_emis_output); } const_view_1d &col_latitudes = col_latitudes_; const_view_1d &col_longitudes = col_longitudes_; @@ -797,7 +788,6 @@ void MAMMicrophysics::run_impl(const double dt) { constexpr int gas_pcnst = mam_coupling::gas_pcnst(); constexpr int nqtendbb = mam_coupling::nqtendbb(); - constexpr auto adv_mass = mam4::gas_chemistry::adv_mass; constexpr int pcnst = mam4::pcnst; const auto vert_emis_output = vert_emis_output_; const auto extfrc = extfrc_; @@ -806,6 +796,12 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: remove this hard-code value const int offset_aerosol = mam4::utils::gasses_start_ind(); + Real adv_mass_kg_per_moles[gas_pcnst]; + for(int i = 0; i < gas_pcnst; ++i) + { + adv_mass_kg_per_moles[i] = mam4::gas_chemistry::adv_mass[i]/1e3; + } + // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for( @@ -927,11 +923,6 @@ void MAMMicrophysics::run_impl(const double dt) { Real qqcw_long[pcnst] = {}; mam4::utils::extract_stateq_from_prognostics(progs,atm, state_q, k); - // std::cout << "Before state_q: "; - // for (int i = 0; i < pcnst; ++i) { - // std::cout << state_q[i] << " "; - // } - mam4::utils::extract_qqcw_from_prognostics(progs,qqcw_long,k); for (int i = offset_aerosol; i < pcnst; ++i) { @@ -944,8 +935,8 @@ void MAMMicrophysics::run_impl(const double dt) { // CHECK: convert_work_arrays_to_vmr and mmr2vmr should produce the same ouputs // However, in mmr2vmr we do not iterate to get species value. // mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); - mam_coupling::mmr2vmr(q,adv_mass, vmr); - mam_coupling::mmr2vmr(qqcw,adv_mass,vmrcw); + mam_coupling::mmr2vmr(q,adv_mass_kg_per_moles, vmr); + mam_coupling::mmr2vmr(qqcw,adv_mass_kg_per_moles,vmrcw); // aerosol/gas species tendencies (output) Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; @@ -965,25 +956,12 @@ void MAMMicrophysics::run_impl(const double dt) { // Gas Phase Chemistry //--------------------- // - Real photo_rates_k[mam4::mo_photo::phtcnt]; - for(int i = 0; i < mam4::mo_photo::phtcnt; ++i) { - photo_rates_k[i] = photo_rates_icol(k, i); - } - - // Real extfrc_k[mam4::gas_chemistry::extcnt]; - // for (int i = 0; i < mam4::gas_chemistry::extcnt; ++i) { - // extfrc_k[i] = extfrc_icol(k, i); - // } - const auto& extfrc_k = ekat::subview(extfrc_icol,k); - - Real invariants_k[mam4::gas_chemistry::nfs]; - for(int i = 0; i < mam4::gas_chemistry::nfs; ++i) { - invariants_k[i] = invariants_icol(k, i); - } - + const auto& extfrc_k = ekat::subview(extfrc_icol,k); + const auto& invariants_k = ekat::subview(invariants_icol,k); + const auto& photo_rates_k = ekat::subview(photo_rates_icol,k); impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k, extfrc_k.data(), invariants_k, vmr); - + photo_rates_k.data(), extfrc_k.data(), + invariants_k.data(), vmr); //---------------------- // Aerosol microphysics //---------------------- @@ -1004,14 +982,23 @@ void MAMMicrophysics::run_impl(const double dt) { // * dry and wet diameters [m] // * wet densities [kg/m3] // * aerosol water mass mixing ratio [kg/kg] - Real dgncur_a[num_modes] = {}; - Real dgncur_awet[num_modes] = {}; - Real wetdens[num_modes] = {}; - Real qaerwat[num_modes] = {}; - - impl::compute_water_content(progs, atm, k, qv, temp, pmid, dgncur_a, + // FIXME:!!! These values are inputs for this interface + // We need to get these values from the FM. + Real dgncur_a[num_modes] = {1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08 }; + Real dgncur_awet[num_modes] = {1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08}; + Real wetdens[num_modes] = {1193.43 ,1188.03 ,1665.08 ,1044.58 }; + Real qaerwat[num_modes] = {5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22}; +# if 0 + Real n_mode_i[num_modes]; + for (int i = 0; i < num_modes; ++i) { + n_mode_i[i] = progs.n_mode_i[i](k); + } + // FIXME: We do not need to invoked this function in this interface. + impl::compute_water_content(state_q, qqcw_long, qv, temp, pmid, + n_mode_i, dgncur_a, dgncur_awet, wetdens, qaerwat); +#endif // do aerosol microphysics (gas-aerosol exchange, nucleation, // coagulation) impl::modal_aero_amicphys_intr( @@ -1058,25 +1045,15 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: C++ port in progress! // mam4::drydep::drydep_xactive(...); - mam_coupling::vmr2mmr(vmr,adv_mass, q); - mam_coupling::vmr2mmr(vmrcw,adv_mass,qqcw); + mam_coupling::vmr2mmr(vmr, adv_mass_kg_per_moles, q); + mam_coupling::vmr2mmr(vmrcw, adv_mass_kg_per_moles, qqcw); for (int i = offset_aerosol; i < pcnst; ++i) { state_q[i] = q[i-offset_aerosol]; qqcw_long[i] = qqcw[i-offset_aerosol]; } - mam4::utils::inject_stateq_to_prognostics(state_q,progs,k); - // std::cout << "state_q: "; - // for (int i = 0; i < pcnst; ++i) { - // std::cout << state_q[i] << " "; - // } - // std::cout << std::endl; - - mam4::utils::inject_qqcw_to_prognostics(qqcw_long, progs,k); - // transfer updated prognostics from work arrays - // mam_coupling::convert_work_arrays_to_mmr(vmr, vmrcw, q, qqcw); - // mam_coupling::transfer_work_arrays_to_prognostics(q, qqcw, - // progs, k); + mam4::utils::inject_stateq_to_prognostics(state_q, progs, k); + mam4::utils::inject_qqcw_to_prognostics(qqcw_long, progs, k); }); }); diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 5e758936595..73188d36ff9 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -281,7 +281,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::vector vert_emis_data_end_; std::vector vert_emis_data_beg_; std::vector vert_emis_data_out_; - std::vector vert_emis_altitude_int_; std::map< std::string, std::string >vert_emis_file_name_; std::map< std::string, std::vector > vert_emis_var_names_; view_2d vert_emis_output_[mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS]; diff --git a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp b/components/eamxx/src/physics/mam/impl/compute_water_content.cpp index d651e860387..f6493ff7371 100644 --- a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp +++ b/components/eamxx/src/physics/mam/impl/compute_water_content.cpp @@ -3,67 +3,93 @@ namespace scream::impl { KOKKOS_INLINE_FUNCTION -void compute_water_content(const mam4::Prognostics &progs, - const haero::Atmosphere &atm, int k, Real qv, - Real temp, Real pmid, - Real dgncur_a[mam4::AeroConfig::num_modes()], - Real dgncur_awet[mam4::AeroConfig::num_modes()], - Real wetdens[mam4::AeroConfig::num_modes()], - Real qaerwat[mam4::AeroConfig::num_modes()]) { +void compute_water_content( Real *state_q, // in + const Real *qqcw, // in, + Real qv,// in + Real temp,// in + Real pmid, // in + const Real num_k_a[mam4::AeroConfig::num_modes()],// in + Real dgncur_a[mam4::AeroConfig::num_modes()], // out + Real dgncur_awet[mam4::AeroConfig::num_modes()],// out + Real wetdens[mam4::AeroConfig::num_modes()],// out + Real qaerwat[mam4::AeroConfig::num_modes()]// out + ) { constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int num_aero_ids = mam4::AeroConfig::num_aerosol_ids(); + constexpr int pcnst = mam4::pcnst; + // get some information about aerosol species // FIXME: this isn't great! + int numptr_amode[num_modes]; + int mam_idx[num_modes][ndrop::nspec_max]; + int mam_cnst_idx[num_modes][ndrop::nspec_max]; constexpr int maxd_aspectype = mam4::water_uptake::maxd_aspectype; int nspec_amode[num_modes], lspectype_amode[maxd_aspectype][num_modes]; Real specdens_amode[maxd_aspectype], spechygro[maxd_aspectype]; - mam4::water_uptake::get_e3sm_parameters(nspec_amode, lspectype_amode, - specdens_amode, spechygro); + int lmassptr_amode[ndrop::maxd_aspectype][num_modes]; + mam4::ndrop::get_e3sm_parameters(nspec_amode, lspectype_amode, lmassptr_amode, + numptr_amode, specdens_amode, spechygro, mam_idx, + mam_cnst_idx); + + Real inv_density[num_modes][AeroConfig::num_aerosol_ids()] = {}; + Real num2vol_ratio_min[num_modes] = {}; + Real num2vol_ratio_max[num_modes] = {}; + Real num2vol_ratio_max_nmodes[num_modes] = {}; + Real num2vol_ratio_min_nmodes[num_modes] = {}; + Real num2vol_ratio_nom_nmodes[num_modes] = {}; + Real dgnmin_nmodes[num_modes] = {}; + Real dgnmax_nmodes[num_modes] = {}; + Real dgnnom_nmodes[num_modes] = {}; + // outputs + bool noxf_acc2ait[AeroConfig::num_aerosol_ids()] = {}; + int n_common_species_ait_accum = {}; + int ait_spec_in_acc[AeroConfig::num_aerosol_ids()] = {}; + int acc_spec_in_ait[AeroConfig::num_aerosol_ids()] = {}; + Real mean_std_dev_nmodes[num_modes]; + mam4::modal_aero_calcsize::init_calcsize( + inv_density, num2vol_ratio_min, num2vol_ratio_max, + num2vol_ratio_max_nmodes, num2vol_ratio_min_nmodes, + num2vol_ratio_nom_nmodes, dgnmin_nmodes, dgnmax_nmodes, dgnnom_nmodes, + mean_std_dev_nmodes, + // outputs + noxf_acc2ait, n_common_species_ait_accum, ait_spec_in_acc, + acc_spec_in_ait); // extract aerosol tracers for this level into state_q, which is needed // for computing dry aerosol properties below // FIXME: we should eliminate this index translation stuff - constexpr int nvars = aero_model::pcnst; - Real state_q[nvars]; // aerosol tracers for level k - mam4::utils::extract_stateq_from_prognostics(progs, - atm,state_q, - k); - // compute the dry volume for each mode, and from it the current dry // geometric nominal particle diameter. // FIXME: We have to do some gymnastics here to set up the calls to // FIXME: calcsize. This could be improved. - Real inv_densities[num_modes][num_aero_ids] = {}; - for(int imode = 0; imode < num_modes; ++imode) { - const int n_spec = mam4::num_species_mode(imode); - for(int ispec = 0; ispec < n_spec; ++ispec) { - const int iaer = static_cast(mam4::mode_aero_species(imode, ispec)); - const Real density = mam4::aero_species(iaer).density; - inv_densities[imode][ispec] = 1.0 / density; - } - } + for(int imode = 0; imode < num_modes; ++imode) { - Real dryvol_i, dryvol_c; // interstitial and cloudborne dry volumes - mam4::calcsize::compute_dry_volume_k(k, imode, inv_densities, progs, - dryvol_i, dryvol_c); + const auto v2nmin = num2vol_ratio_min[imode]; + const auto v2nmax = num2vol_ratio_max[imode]; + const auto dgnmin = dgnmin_nmodes[imode]; + const auto dgnmax = dgnmax_nmodes[imode]; + const auto mean_std_dev = mean_std_dev_nmodes[imode]; + + Real dryvol_i, dryvol_c = 0.0; // interstitial and cloudborne dry volumes + mam4::modal_aero_calcsize::compute_dry_volume(imode, // in + state_q, // in + qqcw, // in + inv_density, // in + lmassptr_amode, + dryvol_i, // out + dryvol_c); // NOTE: there's some disagreement over whether vol2num should be called // NOTE: num2vol here, so I'm just adopting the nomenclature used by // NOTE: the following call to calcsize) - const mam4::Mode &mode = mam4::modes(imode); - Real vol2num_min = - 1.0 / mam4::conversions::mean_particle_volume_from_diameter( - mode.max_diameter, mode.mean_std_dev); - Real vol2num_max = - 1.0 / mam4::conversions::mean_particle_volume_from_diameter( - mode.min_diameter, mode.mean_std_dev); - Real vol2num; - mam4::calcsize::update_diameter_and_vol2num( - dryvol_i, progs.n_mode_i[imode](k), vol2num_min, vol2num_max, - mode.min_diameter, mode.max_diameter, mode.mean_std_dev, - dgncur_a[imode], vol2num); + Real num2vol_ratio_cur_i=0; + // Make it non-negative + auto num_i_k = num_k_a[imode] < 0 ? 0 : num_k_a[imode]; + calcsize::update_diameter_and_vol2num(dryvol_i, num_i_k, v2nmin, v2nmax, + dgnmin, dgnmax, mean_std_dev, + dgncur_a[imode], num2vol_ratio_cur_i); } // calculate dry aerosol properties diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index a4bebe03278..6c452db84a2 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -90,11 +90,18 @@ struct TracerData { // We cannot use a std::vector // because we need to access these views from device. view_2d data[MAX_NVARS_TRACER]; + // type of file + TracerFileType file_type; + + // These views are employed in files with the PS variable view_1d ps; const_view_1d hyam; const_view_1d hybm; view_int_1d work_vert_inter[MAX_NVARS_TRACER];; - TracerFileType file_type; + + // External forcing file (vertical emission) + // Uses altitude instead of pressure to interpolate data + view_1d altitude_int; void allocate_data_views() { EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); @@ -130,7 +137,7 @@ struct TracerData { "Error! file does have the PS variable. \n"); ps = ps_in; } - + // FIXME: The same file is being opened more than twice in the microphysics module.. void set_hyam_n_hybm(const std::shared_ptr &horiz_remapper, const std::string &tracer_file_name) { EKAT_REQUIRE_MSG(file_type == FORMULA_PS, @@ -167,21 +174,19 @@ struct TracerData { } }; -inline const_view_1d get_altitude_int( - const std::shared_ptr &horiz_remapper, - const std::string &tracer_file_name) { - // Read in hyam/hybm in start/end data - auto nondim = ekat::units::Units::nondimensional(); - const auto io_grid = horiz_remapper->get_src_grid(); - Field altitude_int_f(FieldIdentifier("altitude_int", - io_grid->get_vertical_layout(false), - nondim, io_grid->name())); - altitude_int_f.allocate_view(); - AtmosphereInput hvcoord_reader(tracer_file_name, io_grid, {altitude_int_f}, - true); - hvcoord_reader.read_variables(); - hvcoord_reader.finalize(); - return altitude_int_f.get_view(); +// Given a filename, return altitude_int +inline void get_altitude_int( + const std::string &tracer_file_name, + view_1d& altitude_int) { +// in tracer_file_name: NC file name +// out altitude_int: views of int altitude + scorpio::register_file(tracer_file_name, scorpio::Read); + const int nlevs_data = scorpio::get_dimlen(tracer_file_name, "altitude_int"); + view_1d_host altitude_int_host("altitude_int_host", nlevs_data); + scorpio::read_var(tracer_file_name, "altitude_int", altitude_int_host.data()); + scorpio::release_file(tracer_file_name); + altitude_int = view_1d("altitude_int",nlevs_data); + Kokkos::deep_copy(altitude_int, altitude_int_host); } // set_altitude_int // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp @@ -433,7 +438,7 @@ inline std::shared_ptr create_tracer_data_reader( } // create_tracer_data_reader inline void update_tracer_data_from_file( - std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, + std::shared_ptr &scorpio_reader, const int time_index, // zero-based AbstractRemapper &tracer_horiz_interp, TracerData &tracer_data) { // 1. read from field @@ -482,6 +487,11 @@ inline void update_tracer_timestate( for(int ivar = 0; ivar < nvars; ++ivar) { Kokkos::deep_copy(tracer_beg[ivar], tracer_end[ivar]); } + + // Following SPA to time-interpolate data in MAM4xx + // Assume the data is saved monthly and cycles in one year + // Add offset_time_index to support cases where data is saved + // from other periods of time. // Update the SPA forcing data for this month and next month // Start by copying next months data to this months data structure. // NOTE: If the timestep is bigger than monthly this could cause the wrong @@ -489,7 +499,7 @@ inline void update_tracer_timestate( // to be assigned. A timestep greater than a month is very unlikely // so we will proceed. int next_month = time_state.offset_time_index + (time_state.current_month + 1) % 12; - update_tracer_data_from_file(scorpio_reader, ts, next_month, + update_tracer_data_from_file(scorpio_reader, next_month, tracer_horiz_interp, data_tracer_end); } @@ -807,7 +817,7 @@ inline void advance_tracer_data( TracerTimeState &time_state, TracerData &data_tracer_beg, TracerData &data_tracer_end, TracerData &data_tracer_out, const view_2d &p_src, const const_view_2d &p_tgt, - const const_view_1d &zi_src, const const_view_2d &zi_tgt, + const const_view_2d &zi_tgt, const view_2d output[]) { /* Update the TracerTimeState to reflect the current time, note the addition * of dt */ @@ -830,7 +840,7 @@ inline void advance_tracer_data( data_tracer_out.file_type == ZONAL) { perform_vertical_interpolation(p_src, p_tgt, data_tracer_out, output); } else if(data_tracer_out.file_type == VERT_EMISSION) { - perform_vertical_interpolation(zi_src, zi_tgt, data_tracer_out, output); + perform_vertical_interpolation(data_tracer_end.altitude_int, zi_tgt, data_tracer_out, output); } } // advance_tracer_data diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 32d477f3fda..5ec5ee5fe7f 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -12,7 +12,7 @@ atmosphere_processes: atm_procs_list: [mam4_aero_microphys] mam4_aero_microphys: mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc - mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc + mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_ne2np4_L26_1850-2015_c20240827.nc mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc mam4_chlorine_loading_ymd : 20100101 mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc From 73639019abb4cc6a5638e7f46e0dcedf00de6bb0 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 30 Aug 2024 14:20:43 -0600 Subject: [PATCH 18/31] Retrieve dgncur_a, dgncur_awet, wetdens, and qaerwat from FM --- ...mxx_mam_microphysics_process_interface.cpp | 85 ++++++++++++------- .../src/physics/mam/impl/helper_micro.hpp | 70 +-------------- .../mam/aero_microphys/input.yaml | 7 ++ 3 files changed, 62 insertions(+), 100 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 79ab694a982..041fc535734 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -130,6 +130,24 @@ void MAMMicrophysics::set_grids( // At interfaces FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; + const int nmodes = mam4::AeroConfig::num_modes(); // Number of modes + // layout for 3D (ncol, nmodes, nlevs) + FieldLayout scalar3d_mid_nmodes = + grid_->get_3d_vector_layout(true, nmodes, "nmodes"); + + static constexpr auto m3 = m * m * m; + // Aerosol dry particle diameter [m] + add_field("dgncur_a", scalar3d_mid_nmodes, m, grid_name); + + // Wet aerosol density [kg/m3] + add_field("wetdens", scalar3d_mid_nmodes, kg / m3, grid_name); + + // Aerosol water [kg/kg] + add_field("qaerwat", scalar3d_mid_nmodes, kg / kg, grid_name); + + // Wet Required diameter [m] + add_field("dgnumwet", scalar3d_mid_nmodes, m, grid_name); + // define fields needed in mam4xx // atmospheric quantities @@ -230,12 +248,11 @@ void MAMMicrophysics::set_grids( } linoz_data_beg_.set_file_type(tracer_file_type); linoz_data_end_.set_file_type(tracer_file_type); - // FIXME: get this from input.yaml - int cyclical_ymd=20100100; //in format YYYYMMDD + // int linoz_cyclical_ymd=20100100; //in format YYYYMMDD + int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); std::vector linoz_dates; int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(linoz_file_name_, cyclical_ymd, cyclical_ymd_index, linoz_dates); - trace_time_state_.offset_time_index=cyclical_ymd_index; + scream::mam_coupling::get_time_from_ncfile(linoz_file_name_, linoz_cyclical_ymd, cyclical_ymd_index, linoz_dates); linoz_time_state_.offset_time_index=cyclical_ymd_index; } { @@ -254,11 +271,11 @@ void MAMMicrophysics::set_grids( } tracer_data_beg_.set_file_type(tracer_file_type); tracer_data_end_.set_file_type(tracer_file_type); - // FIXME: get this from input.yaml - int cyclical_ymd=20150101; //in format YYYYMMDD + // int cyclical_ymd=20150101; //in format YYYYMMDD + int oxid_ymd = m_params.get("mam4_oxid_ymd"); std::vector oxi_dates; int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(oxid_file_name_, cyclical_ymd, cyclical_ymd_index, oxi_dates); + scream::mam_coupling::get_time_from_ncfile(oxid_file_name_, oxid_ymd, cyclical_ymd_index, oxi_dates); trace_time_state_.offset_time_index=cyclical_ymd_index; } @@ -315,11 +332,11 @@ void MAMMicrophysics::set_grids( { // NOTE: Here I am assuming all vert file have same times. - // FIXME: get this from input.yaml - int cyclical_ymd=20100101; //in format YYYYMMDD + // int cyclical_ymd=20100101; //in format YYYYMMDD + int verti_emiss_cyclical_ymd = m_params.get("verti_emiss_ymd"); std::vector vertical_emiss_dates; int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(vert_emis_file_name_["num_a4"], cyclical_ymd, cyclical_ymd_index, vertical_emiss_dates); + scream::mam_coupling::get_time_from_ncfile(vert_emis_file_name_["num_a4"], verti_emiss_cyclical_ymd, cyclical_ymd_index, vertical_emiss_dates); vert_emiss_time_state_.offset_time_index=cyclical_ymd_index; } } @@ -402,7 +419,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { dry_atm_.qi = buffer_.qi_dry; dry_atm_.ni = buffer_.ni_dry; dry_atm_.w_updraft = buffer_.w_updraft; - dry_atm_.z_surf = 0.0; // FIXME: for now + dry_atm_.z_surf = 0.0; // It is always zero. // get surface albedo: shortwave, direct d_sfc_alb_dir_vis_ = get_field_in("sfc_alb_dir_vis").get_view(); @@ -460,9 +477,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { m_params.get("mam4_xs_long_file"); photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); - - // FIXME: read relevant land use data from drydep surface file - // set up our preprocess/postprocess functors preprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, dry_aero_); @@ -669,6 +683,11 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::parallel_for("preprocess", scan_policy, preprocess_); Kokkos::fence(); + const auto wet_geometric_mean_diameter_i = get_field_in("dgnumwet").get_view(); + const auto dry_geometric_mean_diameter_i = get_field_in("dgncur_a").get_view(); + const auto qaerwat = get_field_in("qaerwat").get_view(); + const auto wetdens = get_field_in("wetdens").get_view(); + // reset internal WSM variables // workspace_mgr_.reset_internals(); @@ -821,6 +840,14 @@ void MAMMicrophysics::run_impl(const double dt) { auto z_iface = ekat::subview(dry_atm.z_iface, icol); Real phis = dry_atm.phis(icol); + auto wet_diameter_icol = + ekat::subview(wet_geometric_mean_diameter_i, icol); + auto dry_diameter_icol = + ekat::subview(dry_geometric_mean_diameter_i, icol); + auto qaerwat_icol = ekat::subview(qaerwat, icol); + auto wetdens_icol = ekat::subview(wetdens, icol); + + // set surface state data haero::Surface sfc{}; @@ -982,30 +1009,24 @@ void MAMMicrophysics::run_impl(const double dt) { // * dry and wet diameters [m] // * wet densities [kg/m3] // * aerosol water mass mixing ratio [kg/kg] - // FIXME:!!! These values are inputs for this interface - // We need to get these values from the FM. - Real dgncur_a[num_modes] = {1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08 }; - Real dgncur_awet[num_modes] = {1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08}; - Real wetdens[num_modes] = {1193.43 ,1188.03 ,1665.08 ,1044.58 }; - Real qaerwat[num_modes] = {5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22}; -# if 0 - Real n_mode_i[num_modes]; - for (int i = 0; i < num_modes; ++i) { - n_mode_i[i] = progs.n_mode_i[i](k); + Real dgncur_a_kk[num_modes] = {}; + Real dgncur_awet_kk[num_modes] = {}; + Real wetdens_kk[num_modes] = {}; + Real qaerwat_kk[num_modes] = {}; + + for (int imode = 0; imode < num_modes; imode++) { + dgncur_awet_kk[imode] = wet_diameter_icol(imode, k); + dgncur_a_kk[imode] = dry_diameter_icol(imode, k); + qaerwat_kk[imode] = qaerwat_icol(imode, k); + wetdens_kk[imode] = wetdens_icol(imode, k); } - // FIXME: We do not need to invoked this function in this interface. - impl::compute_water_content(state_q, qqcw_long, qv, temp, pmid, - n_mode_i, dgncur_a, - dgncur_awet, wetdens, qaerwat); - -#endif // do aerosol microphysics (gas-aerosol exchange, nucleation, // coagulation) impl::modal_aero_amicphys_intr( config.amicphys, step, dt, temp, pmid, pdel, zm, pblh, qv, cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, - vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a, - dgncur_awet, wetdens, qaerwat); + vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a_kk, + dgncur_awet_kk, wetdens_kk, qaerwat_kk); //----------------- // LINOZ chemistry //----------------- diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 6c452db84a2..0f0179362ab 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -665,76 +665,10 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, }); } -#if 0 -inline void perform_vertical_interpolation(const view_2d &p_src_c, - const const_view_2d &p_tgt_c, - const TracerData &input, - const view_2d output[]) { - // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_ == output[0].extent(0)); - -#if 1 - // FIXME: I was encountering a compilation error when using const_view_2d. - // The issue is fixed by https://github.com/E3SM-Project/EKAT/pull/346. - // I will keep this code until this PR is merged into the EKAT master branch - // and we update the EKAT version in our code. I am converting const_view_2d - // to view_2d. - auto p_src_ptr = (Real *)p_src_c.data(); - view_2d p_src(p_src_ptr, p_src_c.extent(0), p_src_c.extent(1)); - auto p_tgt_ptr = (Real *)p_tgt_c.data(); - view_2d p_tgt(p_tgt_ptr, p_tgt_c.extent(0), p_tgt_c.extent(1)); -#else - const auto p_src = p_src_c; - const auto p_tgt = p_tgt_c; -#endif - - const int ncols = input.ncol_; - // FIXME: I am getting FPEs if I do not subtract 1 from nlevs_src. - const int nlevs_src = input.nlev_ - 1; - const int nlevs_tgt = output[0].extent(1); - - LIV vert_interp(ncols, nlevs_src, nlevs_tgt); - - // We can ||ize over columns as well as over variables and bands - const int num_vars = input.nvars_; - const int num_vert_packs = nlevs_tgt; - const auto policy_setup = ESU::get_default_team_policy(ncols, num_vert_packs); - - // Setup the linear interpolation object - Kokkos::parallel_for( - "tracer_vert_interp_setup_loop", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const &team) { - const int icol = team.league_rank(); - // Setup - vert_interp.setup(team, ekat::subview(p_src, icol), - ekat::subview(p_tgt, icol)); - }); - Kokkos::fence(); - - // Now use the interpolation object in || over all variables. - const int outer_iters = ncols * num_vars; - const auto policy_interp = - ESU::get_default_team_policy(outer_iters, num_vert_packs); - Kokkos::parallel_for( - "tracer_vert_interp_loop", policy_interp, - KOKKOS_LAMBDA(typename LIV::MemberType const &team) { - const int icol = team.league_rank() / num_vars; - const int ivar = team.league_rank() % num_vars; - - const auto x1 = ekat::subview(p_src, icol); - const auto x2 = ekat::subview(p_tgt, icol); - - const auto y1 = ekat::subview(input.data[ivar], icol); - const auto y2 = ekat::subview(output[ivar], icol); - - vert_interp.lin_interp(team, x1, x2, y1, y2, icol); - }); - Kokkos::fence(); -} -#endif // rebin is a port from: // https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 -inline void rebin(int nsrc, int ntrg, const const_view_1d &src_x, +KOKKOS_INLINE_FUNCTION +void rebin(int nsrc, int ntrg, const const_view_1d &src_x, const Real trg_x[], const view_1d &src, const view_1d &trg) { for(int i = 0; i < ntrg; ++i) { Real tl = trg_x[i]; diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 5ec5ee5fe7f..fe167ed67a7 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -11,12 +11,15 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] mam4_aero_microphys: + mam4_linoz_ymd : 20100101 mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_ne2np4_L26_1850-2015_c20240827.nc + mam4_oxid_ymd : 20150101 mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc mam4_chlorine_loading_ymd : 20100101 mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + verti_emiss_ymd : 20100101 mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726.nc mam4_so4_a1_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20240823.nc mam4_so4_a2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20240823.nc @@ -43,6 +46,10 @@ initial_conditions: topography_filename: ${TOPO_DATA_DIR}/${EAMxx_tests_TOPO_FILE} phis : 1.0 pbl_height: 1.0 + dgncur_a : [1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08] + dgnumwet : [1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08] + qaerwat : [1193.43 ,1188.03 ,1665.08 ,1044.58] + wetdens : [5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22] #These should come from the input file # The parameters for I/O control From bf031321f2fa23cc1389137dc8c3921276fbb929 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Sun, 1 Sep 2024 20:48:56 -0600 Subject: [PATCH 19/31] Using one data structure to store beginning, end, and output data sets. --- ...mxx_mam_microphysics_process_interface.cpp | 261 ++++++++---------- ...mxx_mam_microphysics_process_interface.hpp | 12 +- .../src/physics/mam/impl/helper_micro.hpp | 138 ++++----- 3 files changed, 191 insertions(+), 220 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 041fc535734..a40a33b4bc5 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -237,46 +237,79 @@ void MAMMicrophysics::set_grids( std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col", "cariolle_pscs"}; - TracerFileType tracer_file_type; + LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, linoz_file_name_, spa_map_file, var_names, tracer_file_type); + grid_, linoz_file_name_, spa_map_file, var_names, linoz_data_.file_type); LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader( LinozHorizInterp_, linoz_file_name_); - linoz_data_out_.set_file_type(tracer_file_type); - if(tracer_file_type == TracerFileType::FORMULA_PS) { - linoz_data_out_.set_hyam_n_hybm(LinozHorizInterp_, linoz_file_name_); - } - linoz_data_beg_.set_file_type(tracer_file_type); - linoz_data_end_.set_file_type(tracer_file_type); + // int linoz_cyclical_ymd=20100100; //in format YYYYMMDD int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); std::vector linoz_dates; int cyclical_ymd_index=-1; scream::mam_coupling::get_time_from_ncfile(linoz_file_name_, linoz_cyclical_ymd, cyclical_ymd_index, linoz_dates); linoz_time_state_.offset_time_index=cyclical_ymd_index; + + // linoz reader + const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); + const int num_cols_io_linoz = + io_grid_linoz->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_linoz = + io_grid_linoz + ->get_num_vertical_levels(); // Number of levels per column + const int nvars = int(var_names.size());; + linoz_data_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); + linoz_data_.allocate_data_views(); + linoz_data_.allocate_work_vert_inter(); + if(linoz_data_.file_type == TracerFileType::FORMULA_PS) { + linoz_data_.set_hyam_n_hybm(LinozHorizInterp_, linoz_file_name_); + linoz_data_.allocate_ps(); + } else if(linoz_data_.file_type == TracerFileType::ZONAL) { + // we use ncremap and python scripts to convert zonal files to ne4pn4 + // grids. + p_src_linoz_ = + view_2d("pressure_src_invariant", ncol_, num_levs_io_linoz); + scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_, + p_src_linoz_); + } } { oxid_file_name_ = m_params.get("mam4_oxid_file_name"); std::string spa_map_file = ""; //NOTE: order matches mam4xx: std::vector var_names{"O3", "OH", "NO3", "HO2"}; - TracerFileType tracer_file_type; TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, oxid_file_name_, spa_map_file, var_names, tracer_file_type); + grid_, oxid_file_name_, spa_map_file, var_names, tracer_data_.file_type); TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader( TracerHorizInterp_, oxid_file_name_); - tracer_data_out_.set_file_type(tracer_file_type); - if(tracer_file_type == TracerFileType::FORMULA_PS) { - tracer_data_out_.set_hyam_n_hybm(TracerHorizInterp_, oxid_file_name_); - } - tracer_data_beg_.set_file_type(tracer_file_type); - tracer_data_end_.set_file_type(tracer_file_type); // int cyclical_ymd=20150101; //in format YYYYMMDD int oxid_ymd = m_params.get("mam4_oxid_ymd"); std::vector oxi_dates; int cyclical_ymd_index=-1; scream::mam_coupling::get_time_from_ncfile(oxid_file_name_, oxid_ymd, cyclical_ymd_index, oxi_dates); trace_time_state_.offset_time_index=cyclical_ymd_index; + + const int nvars = int(var_names.size());; + const auto io_grid = TracerHorizInterp_->get_src_grid(); + const int num_cols_io = + io_grid->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io = + io_grid->get_num_vertical_levels(); // Number of levels per column + tracer_data_.init(num_cols_io, num_levs_io, nvars); + tracer_data_.allocate_data_views(); + tracer_data_.allocate_ps(); + tracer_data_.allocate_work_vert_inter(); + + if(tracer_data_.file_type == TracerFileType::FORMULA_PS) { + tracer_data_.set_hyam_n_hybm(TracerHorizInterp_, oxid_file_name_); + } + + p_src_invariant_ = + view_2d("pressure_src_invariant", num_cols_io, num_levs_io); + + for(int ivar = 0; ivar < nvars; ++ivar) { + cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); + } } { @@ -313,21 +346,14 @@ void MAMMicrophysics::set_grids( const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; - TracerFileType tracer_file_type; + scream::mam_coupling::TracerData data_tracer; auto hor_rem = scream::mam_coupling::create_horiz_remapper( - grid_, file_name, spa_map_file, var_names, tracer_file_type); + grid_, file_name, spa_map_file, var_names, data_tracer.file_type); auto file_reader = scream::mam_coupling::create_tracer_data_reader(hor_rem, file_name); - scream::mam_coupling::TracerData data_out, data_beg, data_end; - data_out.set_file_type(tracer_file_type); - data_beg.set_file_type(tracer_file_type); - data_end.set_file_type(tracer_file_type); - VertEmissionsHorizInterp_.push_back(hor_rem); VertEmissionsDataReader_.push_back(file_reader); - vert_emis_data_out_.push_back(data_out); - vert_emis_data_beg_.push_back(data_beg); - vert_emis_data_end_.push_back(data_end); + vert_emis_data_.push_back(data_tracer); }// var_name vert emissions { @@ -339,6 +365,50 @@ void MAMMicrophysics::set_grids( scream::mam_coupling::get_time_from_ncfile(vert_emis_file_name_["num_a4"], verti_emiss_cyclical_ymd, cyclical_ymd_index, vertical_emiss_dates); vert_emiss_time_state_.offset_time_index=cyclical_ymd_index; } + + int i=0; + int offset_emis_ver=0; + for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + const auto var_name = *it; + const auto file_name = vert_emis_file_name_[var_name]; + const auto var_names = vert_emis_var_names_[var_name]; + const int nvars = int(var_names.size()); + + forcings_[i].nsectors = nvars; + // I am assuming the order of species in extfrc_lst_. + // Indexing in mam4xx is fortran. + forcings_[i].frc_ndx = i+1; + const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); + const int num_cols_io_emis = + io_grid_emis->get_num_local_dofs(); // Number of columns on this rank + const int num_levs_io_emis = + io_grid_emis + ->get_num_vertical_levels(); // Number of levels per column + vert_emis_data_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); + vert_emis_data_[i].allocate_data_views(); + if(vert_emis_data_[i].file_type == TracerFileType::FORMULA_PS) { + vert_emis_data_[i].allocate_ps(); + forcings_[i].file_alt_data=false; + } else if(vert_emis_data_[i].file_type == + TracerFileType::VERT_EMISSION) { + forcings_[i].file_alt_data=true; + // FIXME: Do not open this file three times + // I am getting zeros for altitude_int if I use AtmosphereInput + scream::mam_coupling::get_altitude_int(file_name, vert_emis_data_[i].altitude_int); + } + + for (int isp = 0; isp < nvars; ++isp) + { + EKAT_REQUIRE_MSG( + offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), + "Error! Number of fields is bigger than MAX_NUM_VERT_EMISSION_FIELDS. Increase the MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); + forcings_[i].offset=offset_emis_ver; + vert_emis_output_[isp+offset_emis_ver] = + view_2d("vert_emis_output_", ncol_, nlev_); + } + offset_emis_ver+=nvars; + } // end i + } } @@ -543,134 +613,28 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // and spa_end will be reloaded from file with the new month. const int curr_month = timestamp().get_month() - 1; // 0-based - { - const int nvars = 4; - const auto io_grid = TracerHorizInterp_->get_src_grid(); - const int num_cols_io = - io_grid->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io = - io_grid->get_num_vertical_levels(); // Number of levels per column + scream::mam_coupling::update_tracer_data_from_file( + LinozDataReader_, + curr_month, + *LinozHorizInterp_, + linoz_data_); - tracer_data_end_.init(num_cols_io, num_levs_io, nvars); - scream::mam_coupling::update_tracer_data_from_file( + scream::mam_coupling::update_tracer_data_from_file( TracerDataReader_, curr_month, *TracerHorizInterp_, - tracer_data_end_); - - tracer_data_beg_.init(num_cols_io, num_levs_io, nvars); - tracer_data_beg_.allocate_data_views(); - tracer_data_beg_.allocate_ps(); - - tracer_data_out_.init(num_cols_io, num_levs_io, nvars); - tracer_data_out_.allocate_data_views(); - tracer_data_out_.allocate_ps(); - tracer_data_out_.allocate_work_vert_inter(); - - p_src_invariant_ = - view_2d("pressure_src_invariant", num_cols_io, num_levs_io); - - for(int ivar = 0; ivar < nvars; ++ivar) { - cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); - } - } + tracer_data_); - // linoz reader - { - const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); - const int num_cols_io_linoz = - io_grid_linoz->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io_linoz = - io_grid_linoz - ->get_num_vertical_levels(); // Number of levels per column - const int nvars = 8; - linoz_data_end_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); + int i=0; + for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { scream::mam_coupling::update_tracer_data_from_file( - LinozDataReader_, curr_month, *LinozHorizInterp_, - linoz_data_end_); - - linoz_data_beg_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); - linoz_data_beg_.allocate_data_views(); - if(linoz_data_beg_.file_type == TracerFileType::FORMULA_PS) { - linoz_data_beg_.allocate_ps(); - } - - linoz_data_out_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); - linoz_data_out_.allocate_data_views(); - linoz_data_out_.allocate_work_vert_inter(); - if(linoz_data_out_.file_type == TracerFileType::FORMULA_PS) { - linoz_data_out_.allocate_ps(); - } else if(linoz_data_out_.file_type == TracerFileType::ZONAL) { - // we use ncremap and python scripts to convert zonal files to ne4pn4 - // grids. - p_src_linoz_ = - view_2d("pressure_src_invariant", ncol_, num_levs_io_linoz); - scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_, - p_src_linoz_); - } - } - - // vertical emissions - { - - int i=0; - int offset_emis_ver=0; - for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { - const auto var_name = *it; - const auto file_name = vert_emis_file_name_[var_name]; - const auto var_names = vert_emis_var_names_[var_name]; - const int nvars = int(var_names.size()); - - forcings_[i].nsectors = nvars; - // I am assuming the order of species in extfrc_lst_. - // Indexing in mam4xx is fortran. - forcings_[i].frc_ndx = i+1; - const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); - const int num_cols_io_emis = - io_grid_emis->get_num_local_dofs(); // Number of columns on this rank - const int num_levs_io_emis = - io_grid_emis - ->get_num_vertical_levels(); // Number of levels per column - vert_emis_data_end_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); - scream::mam_coupling::update_tracer_data_from_file( VertEmissionsDataReader_[i], curr_month, - *VertEmissionsHorizInterp_[i], vert_emis_data_end_[i]); - - vert_emis_data_beg_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); - vert_emis_data_beg_[i].allocate_data_views(); - if(vert_emis_data_beg_[i].file_type == TracerFileType::FORMULA_PS) { - vert_emis_data_beg_[i].allocate_ps(); - } - - vert_emis_data_out_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); - vert_emis_data_out_[i].allocate_data_views(); - if(vert_emis_data_out_[i].file_type == TracerFileType::FORMULA_PS) { - vert_emis_data_out_[i].allocate_ps(); - forcings_[i].file_alt_data=false; - } else if(vert_emis_data_out_[i].file_type == - TracerFileType::VERT_EMISSION) { - forcings_[i].file_alt_data=true; - // FIXME: Do not open this file three times - // I am getting zeros for altitude_int if I use AtmosphereInput - scream::mam_coupling::get_altitude_int(file_name, vert_emis_data_end_[i].altitude_int); - } + *VertEmissionsHorizInterp_[i], vert_emis_data_[i]); + } - for (int isp = 0; isp < nvars; ++isp) - { - EKAT_REQUIRE_MSG( - offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), - "Error! Number of fields is bigger than MAX_NUM_VERT_EMISSION_FIELDS. Increase the MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); - forcings_[i].offset=offset_emis_ver; - vert_emis_output_[isp+offset_emis_ver] = - view_2d("vert_emis_output_", ncol_, nlev_); - } - offset_emis_ver+=nvars; - } // end i + invariants_ = view_3d("invarians", ncol_, nlev_, mam4::gas_chemistry::nfs); constexpr int extcnt = mam4::gas_chemistry::extcnt; extfrc_=view_3d("extfrc_", ncol_, nlev_, extcnt); - } - - invariants_ = view_3d("invarians", ncol_, nlev_, mam4::gas_chemistry::nfs); } void MAMMicrophysics::run_impl(const double dt) { @@ -736,12 +700,12 @@ void MAMMicrophysics::run_impl(const double dt) { trace_time_state_.t_now = ts.frac_of_year_in_days(); scream::mam_coupling::advance_tracer_data( TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, - tracer_data_beg_, tracer_data_end_, tracer_data_out_, p_src_invariant_, + tracer_data_, p_src_invariant_, dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); scream::mam_coupling::advance_tracer_data( LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, - linoz_data_beg_, linoz_data_end_, linoz_data_out_, p_src_linoz_, + linoz_data_, p_src_linoz_, dry_atm_.p_mid, dry_atm_.z_iface, linoz_output); vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); @@ -758,10 +722,11 @@ void MAMMicrophysics::run_impl(const double dt) { } scream::mam_coupling::advance_tracer_data( VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, - vert_emiss_time_state_, vert_emis_data_beg_[i], vert_emis_data_end_[i], - vert_emis_data_out_[i], p_src_linoz_, dry_atm_.p_mid, + vert_emiss_time_state_, + vert_emis_data_[i], p_src_linoz_, dry_atm_.p_mid, dry_atm_.z_iface, vert_emis_output); } + const_view_1d &col_latitudes = col_latitudes_; const_view_1d &col_longitudes = col_longitudes_; const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 73188d36ff9..7d47e6237a3 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -256,9 +256,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam_coupling::TracerTimeState trace_time_state_; std::shared_ptr TracerDataReader_; std::shared_ptr TracerHorizInterp_; - mam_coupling::TracerData tracer_data_end_; - mam_coupling::TracerData tracer_data_beg_; - mam_coupling::TracerData tracer_data_out_; + mam_coupling::TracerData tracer_data_; view_2d p_src_invariant_; view_3d invariants_; std::string oxid_file_name_; @@ -267,9 +265,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // linoz reader std::shared_ptr LinozDataReader_; std::shared_ptr LinozHorizInterp_; - mam_coupling::TracerData linoz_data_end_; - mam_coupling::TracerData linoz_data_beg_; - mam_coupling::TracerData linoz_data_out_; + mam_coupling::TracerData linoz_data_; view_2d p_src_linoz_; std::string linoz_file_name_; @@ -278,9 +274,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::vector> VertEmissionsDataReader_; std::vector> VertEmissionsHorizInterp_; std::vector extfrc_lst_; - std::vector vert_emis_data_end_; - std::vector vert_emis_data_beg_; - std::vector vert_emis_data_out_; + std::vector vert_emis_data_; std::map< std::string, std::string >vert_emis_file_name_; std::map< std::string, std::vector > vert_emis_var_names_; view_2d vert_emis_output_[mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS]; diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 0f0179362ab..7277135a57b 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -46,6 +46,12 @@ enum TracerFileType { VERT_EMISSION, }; +enum TracerDataIndex { + BEG=0, + END=1, + OUT=2 +}; + /* Maximum number of tracers (or fields) that the tracer reader can handle. Note: We are not allocating memory for MAX_NVARS_TRACER tracers. Therefore, if a file contains more than this number, it is acceptable to @@ -89,12 +95,14 @@ struct TracerData { int nvars_{-1}; // We cannot use a std::vector // because we need to access these views from device. - view_2d data[MAX_NVARS_TRACER]; + // 0: beg 1: end 3: out + view_2d data[3][MAX_NVARS_TRACER]; // type of file TracerFileType file_type; // These views are employed in files with the PS variable - view_1d ps; + // 0: beg 1: end 3: out + view_1d ps[3]; const_view_1d hyam; const_view_1d hybm; view_int_1d work_vert_inter[MAX_NVARS_TRACER];; @@ -108,7 +116,8 @@ struct TracerData { EKAT_REQUIRE_MSG(nlev_ != int(-1), "Error! nlevs has not been set. \n"); for(int ivar = 0; ivar < nvars_; ++ivar) { - data[ivar] = view_2d("linoz_1", ncol_, nlev_); + data[TracerDataIndex::OUT][ivar] = view_2d("linoz_1_out", ncol_, nlev_); + data[TracerDataIndex::BEG][ivar] = view_2d("linoz_1_out", ncol_, nlev_); } } // allocate_data_views @@ -121,22 +130,23 @@ struct TracerData { EKAT_REQUIRE_MSG(file_type == FORMULA_PS, "Error! file does have the PS variable. \n"); - ps = view_1d("ps", ncol_); + ps[TracerDataIndex::OUT] = view_1d("ps", ncol_); + ps[TracerDataIndex::BEG] = view_1d("ps", ncol_); } - void set_data_views(view_2d list_of_views[]) { - for(int ivar = 0; ivar < nvars_; ++ivar) { - EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, - "Error! Insufficient memory size.\n"); - data[ivar] = list_of_views[ivar]; - } - } - - void set_data_ps(const view_1d &ps_in) { - EKAT_REQUIRE_MSG(file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); - ps = ps_in; - } + // void set_data_views(view_2d list_of_views[]) { + // for(int ivar = 0; ivar < nvars_; ++ivar) { + // EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, + // "Error! Insufficient memory size.\n"); + // data[ivar] = list_of_views[ivar]; + // } + // } + + // void set_data_ps(const view_1d &ps_in) { + // EKAT_REQUIRE_MSG(file_type == FORMULA_PS, + // "Error! file does have the PS variable. \n"); + // ps = ps_in; + // } // FIXME: The same file is being opened more than twice in the microphysics module.. void set_hyam_n_hybm(const std::shared_ptr &horiz_remapper, const std::string &tracer_file_name) { @@ -450,31 +460,31 @@ inline void update_tracer_data_from_file( const int nvars = tracer_data.nvars_; // for(int i = 0; i < nvars; ++i) { - tracer_data.data[i] = + tracer_data.data[TracerDataIndex::END][i] = tracer_horiz_interp.get_tgt_field(i).get_view(); } if(tracer_data.file_type == FORMULA_PS) { // Recall, the fields are registered in the order: tracers, ps // 3. Copy from the tgt field of the remapper into the spa_data - tracer_data.ps = + tracer_data.ps[TracerDataIndex::END] = tracer_horiz_interp.get_tgt_field(nvars).get_view(); } } // update_tracer_data_from_file inline void update_tracer_timestate( std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, - AbstractRemapper &tracer_horiz_interp, TracerTimeState &time_state, - TracerData &data_tracer_beg, TracerData &data_tracer_end) { + AbstractRemapper &tracer_horiz_interp, + TracerTimeState &time_state, + TracerData &data_tracer) { // Now we check if we have to update the data that changes monthly // NOTE: This means that SPA assumes monthly data to update. Not // any other frequency. const auto month = ts.get_month() - 1; // Make it 0-based if(month != time_state.current_month) { - // - const auto tracer_beg = data_tracer_beg.data; - const auto tracer_end = data_tracer_end.data; - const int nvars = data_tracer_end.nvars_; + const auto tracer_data = data_tracer.data; + const int nvars = data_tracer.nvars_; + const auto ps = data_tracer.ps; // Update the SPA time state information time_state.current_month = month; @@ -485,7 +495,13 @@ inline void update_tracer_timestate( // Copy spa_end'data into spa_beg'data, and read in the new spa_end for(int ivar = 0; ivar < nvars; ++ivar) { - Kokkos::deep_copy(tracer_beg[ivar], tracer_end[ivar]); + Kokkos::deep_copy(tracer_data[TracerDataIndex::BEG][ivar], + tracer_data[TracerDataIndex::END][ivar]); + } + + if(data_tracer.file_type == FORMULA_PS) { + Kokkos::deep_copy(ps[TracerDataIndex::BEG], + ps[TracerDataIndex::END]); } // Following SPA to time-interpolate data in MAM4xx @@ -500,16 +516,14 @@ inline void update_tracer_timestate( // so we will proceed. int next_month = time_state.offset_time_index + (time_state.current_month + 1) % 12; update_tracer_data_from_file(scorpio_reader, next_month, - tracer_horiz_interp, data_tracer_end); + tracer_horiz_interp, data_tracer); } } // END updata_spa_timestate // This function is based on the SPA::perform_time_interpolation function. inline void perform_time_interpolation(const TracerTimeState &time_state, - const TracerData &data_tracer_beg, - const TracerData &data_tracer_end, - const TracerData &data_tracer_out) { + const TracerData &data_tracer) { // NOTE: we *assume* data_beg and data_end have the *same* hybrid v coords. // IF this ever ceases to be the case, you can interp those too. // Gather time stamp info @@ -518,20 +532,14 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, auto &delta_t = time_state.days_this_month; // We can ||ize over columns as well as over variables and bands - const auto data_beg = data_tracer_beg.data; - const auto data_end = data_tracer_end.data; - const auto data_out = data_tracer_out.data; - - const auto file_type = data_tracer_out.file_type; - - const auto ps_beg = data_tracer_beg.ps; - const auto ps_end = data_tracer_end.ps; - const auto ps_out = data_tracer_out.ps; + const auto data = data_tracer.data; + const auto file_type = data_tracer.file_type; - const int num_vars = data_tracer_end.nvars_; + const auto ps = data_tracer.ps; + const int num_vars = data_tracer.nvars_; - const int ncol = data_tracer_beg.ncol_; - const int num_vert = data_tracer_beg.nlev_; + const int ncol = data_tracer.ncol_; + const int num_vert = data_tracer.nlev_; const int outer_iters = ncol * num_vars; @@ -558,9 +566,9 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, const int ivar = team.league_rank() % num_vars; // Get column of beg/end/out variable - auto var_beg = ekat::subview(data_beg[ivar], icol); - auto var_end = ekat::subview(data_end[ivar], icol); - auto var_out = ekat::subview(data_out[ivar], icol); + auto var_beg = ekat::subview(data[TracerDataIndex::BEG][ivar], icol); + auto var_end = ekat::subview(data[TracerDataIndex::END][ivar], icol); + auto var_out = ekat::subview(data[TracerDataIndex::OUT][ivar], icol); Kokkos::parallel_for( Kokkos::TeamVectorRange(team, num_vert), [&](const int &k) { @@ -569,8 +577,9 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, }); // linoz files do not have ps variables. if(ivar == 1 && file_type == FORMULA_PS) { - ps_out(icol) = - linear_interp(ps_beg(icol), ps_end(icol), delta_t_fraction); + ps[TracerDataIndex::OUT](icol) = + linear_interp(ps[TracerDataIndex::BEG](icol), + ps[TracerDataIndex::END](icol), delta_t_fraction); } }); Kokkos::fence(); @@ -651,6 +660,7 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, EKAT_REQUIRE(work[num_vars-1].data() != 0); // vert_interp is serial in col and lev. const auto policy_setup = ESU::get_default_team_policy(num_vars, 1); + const auto data =input.data; Kokkos::parallel_for( "vert_interp", policy_setup, @@ -658,7 +668,7 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, { const int ivar = team.league_rank(); vert_interp(ncol, levsiz, pver, p_src_c, - p_tgt_c, input.data[ivar], + p_tgt_c, data[TracerDataIndex::OUT][ivar], output[ivar], // work array work[ivar]); @@ -722,6 +732,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const int nsrc = input.nlev_; constexpr int pver = mam4::nlev; const int pverp = pver + 1; + const auto data = input.data; Kokkos::parallel_for( "tracer_vert_interp_loop", policy_interp, @@ -729,7 +740,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const int icol = team.league_rank() / num_vars; const int ivar = team.league_rank() % num_vars; - const auto src = ekat::subview(input.data[ivar], icol); + const auto src = ekat::subview(data[TracerDataIndex::OUT][ivar], icol); const auto trg = ekat::subview(output[ivar], icol); // trg_x @@ -748,8 +759,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, inline void advance_tracer_data( std::shared_ptr &scorpio_reader, AbstractRemapper &tracer_horiz_interp, const util::TimeStamp &ts, - TracerTimeState &time_state, TracerData &data_tracer_beg, - TracerData &data_tracer_end, TracerData &data_tracer_out, + TracerTimeState &time_state, TracerData &data_tracer, const view_2d &p_src, const const_view_2d &p_tgt, const const_view_2d &zi_tgt, const view_2d output[]) { @@ -757,24 +767,26 @@ inline void advance_tracer_data( * of dt */ time_state.t_now = ts.frac_of_year_in_days(); /* Update time state and if the month has changed, update the data.*/ - update_tracer_timestate(scorpio_reader, ts, tracer_horiz_interp, time_state, - data_tracer_beg, data_tracer_end); + update_tracer_timestate(scorpio_reader, ts, + tracer_horiz_interp, + time_state, + data_tracer); // Step 1. Perform time interpolation - perform_time_interpolation(time_state, data_tracer_beg, data_tracer_end, - data_tracer_out); + perform_time_interpolation(time_state, data_tracer); - if(data_tracer_out.file_type == FORMULA_PS) { + if(data_tracer.file_type == FORMULA_PS) { // Step 2. Compute source pressure levels - compute_source_pressure_levels(data_tracer_out.ps, p_src, - data_tracer_out.hyam, data_tracer_out.hybm); + const auto ps = data_tracer.ps[TracerDataIndex::OUT]; + compute_source_pressure_levels(ps, p_src, + data_tracer.hyam, data_tracer.hybm); } // Step 3. Perform vertical interpolation - if(data_tracer_out.file_type == FORMULA_PS || - data_tracer_out.file_type == ZONAL) { - perform_vertical_interpolation(p_src, p_tgt, data_tracer_out, output); - } else if(data_tracer_out.file_type == VERT_EMISSION) { - perform_vertical_interpolation(data_tracer_end.altitude_int, zi_tgt, data_tracer_out, output); + if(data_tracer.file_type == FORMULA_PS || + data_tracer.file_type == ZONAL) { + perform_vertical_interpolation(p_src, p_tgt, data_tracer, output); + } else if(data_tracer.file_type == VERT_EMISSION) { + perform_vertical_interpolation(data_tracer.altitude_int, zi_tgt, data_tracer, output); } } // advance_tracer_data From b15d843cf94e672320c1d90b004a254846a72c5d Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Mon, 2 Sep 2024 17:25:33 -0600 Subject: [PATCH 20/31] Refactor: move allocation of views from the tracer data struct interface to helper code. Combine multiple NC file registrations into a single function call: setup_tracer_data. Making copies of clsmap_4 and permute_4 to fix undefined arrays on the device. --- ...mxx_mam_microphysics_process_interface.cpp | 107 +++---- ...mxx_mam_microphysics_process_interface.hpp | 2 - .../mam/impl/compute_o3_column_density.cpp | 2 - .../mam/impl/compute_water_content.cpp | 115 ------- .../physics/mam/impl/gas_phase_chemistry.cpp | 13 +- .../src/physics/mam/impl/helper_micro.hpp | 285 ++++++++++-------- 6 files changed, 188 insertions(+), 336 deletions(-) delete mode 100644 components/eamxx/src/physics/mam/impl/compute_water_content.cpp diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index a40a33b4bc5..00f987471a6 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -12,7 +12,6 @@ // NOTE: see the impl/ directory for the contents of the impl namespace #include "impl/compute_o3_column_density.cpp" -#include "impl/compute_water_content.cpp" #include "impl/gas_phase_chemistry.cpp" #include "physics/rrtmgp/shr_orb_mod_c2f.hpp" @@ -230,26 +229,21 @@ void MAMMicrophysics::set_grids( // Creating a Linoz reader and setting Linoz parameters involves reading data // from a file and configuring the necessary parameters for the Linoz model. { - // std::string - // linoz_file_name="linoz1850-2015_2010JPL_CMIP6_10deg_58km_c20171109.nc"; linoz_file_name_ = m_params.get("mam4_linoz_file_name"); std::string spa_map_file = ""; std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col", "cariolle_pscs"}; + // in format YYYYMMDD + int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); + scream::mam_coupling::setup_tracer_data(linoz_data_, + linoz_file_name_, linoz_cyclical_ymd); LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, linoz_file_name_, spa_map_file, var_names, linoz_data_.file_type); + grid_, linoz_file_name_, spa_map_file, var_names, linoz_data_); LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader( LinozHorizInterp_, linoz_file_name_); - // int linoz_cyclical_ymd=20100100; //in format YYYYMMDD - int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); - std::vector linoz_dates; - int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(linoz_file_name_, linoz_cyclical_ymd, cyclical_ymd_index, linoz_dates); - linoz_time_state_.offset_time_index=cyclical_ymd_index; - // linoz reader const auto io_grid_linoz = LinozHorizInterp_->get_src_grid(); const int num_cols_io_linoz = @@ -259,35 +253,23 @@ void MAMMicrophysics::set_grids( ->get_num_vertical_levels(); // Number of levels per column const int nvars = int(var_names.size());; linoz_data_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); - linoz_data_.allocate_data_views(); - linoz_data_.allocate_work_vert_inter(); - if(linoz_data_.file_type == TracerFileType::FORMULA_PS) { - linoz_data_.set_hyam_n_hybm(LinozHorizInterp_, linoz_file_name_); - linoz_data_.allocate_ps(); - } else if(linoz_data_.file_type == TracerFileType::ZONAL) { - // we use ncremap and python scripts to convert zonal files to ne4pn4 - // grids. - p_src_linoz_ = - view_2d("pressure_src_invariant", ncol_, num_levs_io_linoz); - scream::mam_coupling::compute_p_src_zonal_files(linoz_file_name_, - p_src_linoz_); - } + linoz_data_.allocate_temporal_views(); } + { oxid_file_name_ = m_params.get("mam4_oxid_file_name"); std::string spa_map_file = ""; //NOTE: order matches mam4xx: std::vector var_names{"O3", "OH", "NO3", "HO2"}; + + // //in format YYYYMMDD + int oxid_ymd = m_params.get("mam4_oxid_ymd"); + scream::mam_coupling::setup_tracer_data(tracer_data_, + oxid_file_name_, oxid_ymd); TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, oxid_file_name_, spa_map_file, var_names, tracer_data_.file_type); + grid_, oxid_file_name_, spa_map_file, var_names, tracer_data_); TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader( TracerHorizInterp_, oxid_file_name_); - // int cyclical_ymd=20150101; //in format YYYYMMDD - int oxid_ymd = m_params.get("mam4_oxid_ymd"); - std::vector oxi_dates; - int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(oxid_file_name_, oxid_ymd, cyclical_ymd_index, oxi_dates); - trace_time_state_.offset_time_index=cyclical_ymd_index; const int nvars = int(var_names.size());; const auto io_grid = TracerHorizInterp_->get_src_grid(); @@ -296,20 +278,12 @@ void MAMMicrophysics::set_grids( const int num_levs_io = io_grid->get_num_vertical_levels(); // Number of levels per column tracer_data_.init(num_cols_io, num_levs_io, nvars); - tracer_data_.allocate_data_views(); - tracer_data_.allocate_ps(); - tracer_data_.allocate_work_vert_inter(); - - if(tracer_data_.file_type == TracerFileType::FORMULA_PS) { - tracer_data_.set_hyam_n_hybm(TracerHorizInterp_, oxid_file_name_); - } - - p_src_invariant_ = - view_2d("pressure_src_invariant", num_cols_io, num_levs_io); + tracer_data_.allocate_temporal_views(); for(int ivar = 0; ivar < nvars; ++ivar) { cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); } + } { @@ -319,8 +293,6 @@ void MAMMicrophysics::set_grids( // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 ','pom_a4 ','bc_a4 ', // 'num_a1 ','num_a2 ','num_a4 ','SOAG ' } // This order corresponds to files in namelist e3smv2 - // Note that I change this order to match extfrc_lst - // 1,9,2,6,3,7,4,5,8 extfrc_lst_=std::vector({"so2","so4_a1","so4_a2","pom_a4","bc_a4", "num_a1","num_a2","num_a4","soag"}); @@ -342,30 +314,24 @@ void MAMMicrophysics::set_grids( vert_emis_var_names_["num_a4"] = {"num_a1_BC_ELEV_BB", "num_a1_POM_ELEV_BB"}; vert_emis_var_names_["soag"] = {"SOAbb_src","SOAbg_src", "SOAff_src"}; + + int verti_emiss_cyclical_ymd = m_params.get("verti_emiss_ymd"); + for (const auto& var_name : extfrc_lst_) { const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; scream::mam_coupling::TracerData data_tracer; + scream::mam_coupling::setup_tracer_data(data_tracer, + file_name, verti_emiss_cyclical_ymd); auto hor_rem = scream::mam_coupling::create_horiz_remapper( - grid_, file_name, spa_map_file, var_names, data_tracer.file_type); + grid_, file_name, spa_map_file, var_names, data_tracer); auto file_reader = scream::mam_coupling::create_tracer_data_reader(hor_rem, file_name); VertEmissionsHorizInterp_.push_back(hor_rem); VertEmissionsDataReader_.push_back(file_reader); vert_emis_data_.push_back(data_tracer); }// var_name vert emissions - - { - // NOTE: Here I am assuming all vert file have same times. - // int cyclical_ymd=20100101; //in format YYYYMMDD - int verti_emiss_cyclical_ymd = m_params.get("verti_emiss_ymd"); - std::vector vertical_emiss_dates; - int cyclical_ymd_index=-1; - scream::mam_coupling::get_time_from_ncfile(vert_emis_file_name_["num_a4"], verti_emiss_cyclical_ymd, cyclical_ymd_index, vertical_emiss_dates); - vert_emiss_time_state_.offset_time_index=cyclical_ymd_index; - } - int i=0; int offset_emis_ver=0; for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { @@ -385,18 +351,8 @@ void MAMMicrophysics::set_grids( io_grid_emis ->get_num_vertical_levels(); // Number of levels per column vert_emis_data_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); - vert_emis_data_[i].allocate_data_views(); - if(vert_emis_data_[i].file_type == TracerFileType::FORMULA_PS) { - vert_emis_data_[i].allocate_ps(); - forcings_[i].file_alt_data=false; - } else if(vert_emis_data_[i].file_type == - TracerFileType::VERT_EMISSION) { - forcings_[i].file_alt_data=true; - // FIXME: Do not open this file three times - // I am getting zeros for altitude_int if I use AtmosphereInput - scream::mam_coupling::get_altitude_int(file_name, vert_emis_data_[i].altitude_int); - } - + vert_emis_data_[i].allocate_temporal_views(); + forcings_[i].file_alt_data=vert_emis_data_[i].has_altitude_; for (int isp = 0; isp < nvars; ++isp) { EKAT_REQUIRE_MSG( @@ -657,7 +613,7 @@ void MAMMicrophysics::run_impl(const double dt) { // NOTE: nothing depends on simulation time (yet), so we can just use zero for // now - double t = 0.0; + // double t = 0.0; // climatology data for linear stratospheric chemistry auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] @@ -700,12 +656,12 @@ void MAMMicrophysics::run_impl(const double dt) { trace_time_state_.t_now = ts.frac_of_year_in_days(); scream::mam_coupling::advance_tracer_data( TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, - tracer_data_, p_src_invariant_, + tracer_data_, dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); scream::mam_coupling::advance_tracer_data( LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, - linoz_data_, p_src_linoz_, + linoz_data_, dry_atm_.p_mid, dry_atm_.z_iface, linoz_output); vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); @@ -723,7 +679,7 @@ void MAMMicrophysics::run_impl(const double dt) { scream::mam_coupling::advance_tracer_data( VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, vert_emiss_time_state_, - vert_emis_data_[i], p_src_linoz_, dry_atm_.p_mid, + vert_emis_data_[i], dry_atm_.p_mid, dry_atm_.z_iface, vert_emis_output); } @@ -731,6 +687,8 @@ void MAMMicrophysics::run_impl(const double dt) { const_view_1d &col_longitudes = col_longitudes_; const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; + + mam_coupling::DryAtmosphere &dry_atm = dry_atm_; mam_coupling::AerosolState &dry_aero = dry_aero_; mam4::mo_photo::PhotoTableData &photo_table = photo_table_; @@ -781,12 +739,15 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: remove this hard-code value const int offset_aerosol = mam4::utils::gasses_start_ind(); Real adv_mass_kg_per_moles[gas_pcnst]; + // NOTE: Making copies of clsmap_4 and permute_4 to fix undefined arrays on the device. + int clsmap_4[gas_pcnst], permute_4[gas_pcnst]; for(int i = 0; i < gas_pcnst; ++i) { adv_mass_kg_per_moles[i] = mam4::gas_chemistry::adv_mass[i]/1e3; + clsmap_4[i]=mam4::gas_chemistry::clsmap_4[i]; + permute_4[i]=mam4::gas_chemistry::permute_4[i]; } - // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for( policy, KOKKOS_LAMBDA(const ThreadTeam &team) { @@ -953,7 +914,7 @@ void MAMMicrophysics::run_impl(const double dt) { const auto& photo_rates_k = ekat::subview(photo_rates_icol,k); impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, photo_rates_k.data(), extfrc_k.data(), - invariants_k.data(), vmr); + invariants_k.data(), clsmap_4, permute_4, vmr); //---------------------- // Aerosol microphysics //---------------------- diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 7d47e6237a3..abf4c023a07 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -257,7 +257,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::shared_ptr TracerDataReader_; std::shared_ptr TracerHorizInterp_; mam_coupling::TracerData tracer_data_; - view_2d p_src_invariant_; view_3d invariants_; std::string oxid_file_name_; view_2d cnst_offline_[4]; @@ -266,7 +265,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { std::shared_ptr LinozDataReader_; std::shared_ptr LinozHorizInterp_; mam_coupling::TracerData linoz_data_; - view_2d p_src_linoz_; std::string linoz_file_name_; // Vertical emission uses 9 files, here I am using std::vector to stote instance of each file. diff --git a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp index 825c861fff6..cf2dbea079e 100644 --- a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp +++ b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp @@ -17,8 +17,6 @@ void compute_o3_column_density(const ThreadTeam &team, // NOTE: if we need o2 column densities, set_ub_col and setcol must be changed Kokkos::parallel_for( Kokkos::TeamThreadRange(team, atm.num_levels()), [&](const int k) { - Real temp = atm.temperature(k); - Real pmid = atm.pressure(k); Real pdel = atm.hydrostatic_dp(k); // ... map incoming mass mixing ratios to working array diff --git a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp b/components/eamxx/src/physics/mam/impl/compute_water_content.cpp deleted file mode 100644 index f6493ff7371..00000000000 --- a/components/eamxx/src/physics/mam/impl/compute_water_content.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include - -namespace scream::impl { - -KOKKOS_INLINE_FUNCTION -void compute_water_content( Real *state_q, // in - const Real *qqcw, // in, - Real qv,// in - Real temp,// in - Real pmid, // in - const Real num_k_a[mam4::AeroConfig::num_modes()],// in - Real dgncur_a[mam4::AeroConfig::num_modes()], // out - Real dgncur_awet[mam4::AeroConfig::num_modes()],// out - Real wetdens[mam4::AeroConfig::num_modes()],// out - Real qaerwat[mam4::AeroConfig::num_modes()]// out - ) { - constexpr int num_modes = mam4::AeroConfig::num_modes(); - constexpr int num_aero_ids = mam4::AeroConfig::num_aerosol_ids(); - constexpr int pcnst = mam4::pcnst; - - - // get some information about aerosol species - // FIXME: this isn't great! - int numptr_amode[num_modes]; - int mam_idx[num_modes][ndrop::nspec_max]; - int mam_cnst_idx[num_modes][ndrop::nspec_max]; - constexpr int maxd_aspectype = mam4::water_uptake::maxd_aspectype; - int nspec_amode[num_modes], lspectype_amode[maxd_aspectype][num_modes]; - Real specdens_amode[maxd_aspectype], spechygro[maxd_aspectype]; - int lmassptr_amode[ndrop::maxd_aspectype][num_modes]; - mam4::ndrop::get_e3sm_parameters(nspec_amode, lspectype_amode, lmassptr_amode, - numptr_amode, specdens_amode, spechygro, mam_idx, - mam_cnst_idx); - - Real inv_density[num_modes][AeroConfig::num_aerosol_ids()] = {}; - Real num2vol_ratio_min[num_modes] = {}; - Real num2vol_ratio_max[num_modes] = {}; - Real num2vol_ratio_max_nmodes[num_modes] = {}; - Real num2vol_ratio_min_nmodes[num_modes] = {}; - Real num2vol_ratio_nom_nmodes[num_modes] = {}; - Real dgnmin_nmodes[num_modes] = {}; - Real dgnmax_nmodes[num_modes] = {}; - Real dgnnom_nmodes[num_modes] = {}; - // outputs - bool noxf_acc2ait[AeroConfig::num_aerosol_ids()] = {}; - int n_common_species_ait_accum = {}; - int ait_spec_in_acc[AeroConfig::num_aerosol_ids()] = {}; - int acc_spec_in_ait[AeroConfig::num_aerosol_ids()] = {}; - Real mean_std_dev_nmodes[num_modes]; - mam4::modal_aero_calcsize::init_calcsize( - inv_density, num2vol_ratio_min, num2vol_ratio_max, - num2vol_ratio_max_nmodes, num2vol_ratio_min_nmodes, - num2vol_ratio_nom_nmodes, dgnmin_nmodes, dgnmax_nmodes, dgnnom_nmodes, - mean_std_dev_nmodes, - // outputs - noxf_acc2ait, n_common_species_ait_accum, ait_spec_in_acc, - acc_spec_in_ait); - - // extract aerosol tracers for this level into state_q, which is needed - // for computing dry aerosol properties below - // FIXME: we should eliminate this index translation stuff - - // compute the dry volume for each mode, and from it the current dry - // geometric nominal particle diameter. - // FIXME: We have to do some gymnastics here to set up the calls to - // FIXME: calcsize. This could be improved. - - for(int imode = 0; imode < num_modes; ++imode) { - const auto v2nmin = num2vol_ratio_min[imode]; - const auto v2nmax = num2vol_ratio_max[imode]; - const auto dgnmin = dgnmin_nmodes[imode]; - const auto dgnmax = dgnmax_nmodes[imode]; - const auto mean_std_dev = mean_std_dev_nmodes[imode]; - - Real dryvol_i, dryvol_c = 0.0; // interstitial and cloudborne dry volumes - mam4::modal_aero_calcsize::compute_dry_volume(imode, // in - state_q, // in - qqcw, // in - inv_density, // in - lmassptr_amode, - dryvol_i, // out - dryvol_c); - - // NOTE: there's some disagreement over whether vol2num should be called - // NOTE: num2vol here, so I'm just adopting the nomenclature used by - // NOTE: the following call to calcsize) - Real num2vol_ratio_cur_i=0; - // Make it non-negative - auto num_i_k = num_k_a[imode] < 0 ? 0 : num_k_a[imode]; - calcsize::update_diameter_and_vol2num(dryvol_i, num_i_k, v2nmin, v2nmax, - dgnmin, dgnmax, mean_std_dev, - dgncur_a[imode], num2vol_ratio_cur_i); - } - - // calculate dry aerosol properties - Real hygro[num_modes], naer[num_modes], dryrad[num_modes], dryvol[num_modes], - drymass[num_modes], rhcrystal[num_modes], rhdeliques[num_modes], - specdens_1[num_modes]; - mam4::water_uptake::modal_aero_water_uptake_dryaer( - nspec_amode, specdens_amode, spechygro, lspectype_amode, state_q, - dgncur_a, hygro, naer, dryrad, dryvol, drymass, rhcrystal, rhdeliques, - specdens_1); - - // calculate wet aerosol properties - Real rh = mam4::conversions::relative_humidity_from_vapor_mixing_ratio( - qv, temp, pmid); - Real wetrad[num_modes], wetvol[num_modes], wtrvol[num_modes]; - mam4::water_uptake::modal_aero_water_uptake_wetaer( - rhcrystal, rhdeliques, dgncur_a, dryrad, hygro, rh, naer, dryvol, wetrad, - wetvol, wtrvol, dgncur_awet, qaerwat); - mam4::water_uptake::modal_aero_water_uptake_wetdens(wetvol, wtrvol, drymass, - specdens_1, wetdens); -} - -} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index d3d77119d3d..ff581db86fb 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -407,6 +407,7 @@ mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, return table; } #endif + // performs gas phase chemistry calculations on a single level of a single // atmospheric column KOKKOS_INLINE_FUNCTION @@ -415,6 +416,8 @@ void gas_phase_chemistry( const Real photo_rates[mam4::mo_photo::phtcnt], // in const Real extfrc[mam4::gas_chemistry::extcnt], // in Real invariants[mam4::gas_chemistry::nfs], // in + const int clsmap_4[mam4::gas_chemistry::gas_pcnst], // in + const int permute_4[mam4::gas_chemistry::gas_pcnst],// in Real q[mam4::gas_chemistry::gas_pcnst]) { // VMRs, inout // constexpr Real rga = 1.0/haero::Constants::gravity; // constexpr Real m2km = 0.01; // converts m -> km @@ -436,16 +439,6 @@ void gas_phase_chemistry( constexpr int itermax = mam4::gas_chemistry::itermax; constexpr int clscnt4 = mam4::gas_chemistry::clscnt4; - constexpr int nfs = mam4::gas_chemistry::nfs; - - // NOTE: vvv these arrays were copied from mam4xx/gas_chem_mechanism.hpp vvv - constexpr int permute_4[gas_pcnst] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; - constexpr int clsmap_4[gas_pcnst] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; - // These indices for species are fixed by the chemical mechanism // std::string solsym[] = {"O3", "H2O2", "H2SO4", "SO2", "DMS", "SOAG", // "so4_a1", "pom_a1", "soa_a1", "bc_a1", "dst_a1", diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 7277135a57b..09badd5695d 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -21,6 +21,39 @@ using ESU = ekat::ExeSpaceUtils; using C = scream::physics::Constants; using LIV = ekat::LinInterp; + +// Linoz NetCDF files use levs instead of formula_terms. +// This function allocates a view, so we need to do it during initialization. +// Thus, we assume that source pressure is independent of time, +// which is the case for Linoz files (zonal file). + +inline void compute_p_src_zonal_files(const view_1d &levs, + const view_2d &p_src) { + EKAT_REQUIRE_MSG(p_src.data() != 0, + "Error: p_src has not been allocated. \n"); + EKAT_REQUIRE_MSG(levs.data() != 0, + "Error: levs has not been allocated. \n"); + const int ncol = p_src.extent(0); + const int nlevs_data = levs.extent(0); + EKAT_REQUIRE_MSG( + int(p_src.extent(1)) == nlevs_data, + "Error: p_src has a different number of levels than the source data. \n"); + + const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); + Kokkos::parallel_for( + "pressure_computation", policy_pressure, KOKKOS_LAMBDA(const Team &team) { + const int icol = team.league_rank(); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), + [&](const Int &kk) { + // mbar->pascals + // FIXME: Does EAMxx have a better method to + // convert units?" + p_src(icol, kk) = levs(kk) * 100; + }); + }); + Kokkos::fence(); +} + // We have a similar version in MAM4xx. // This version was created because the data view cannot be modified // inside the parallel_for. @@ -72,8 +105,6 @@ struct TracerTimeState { Real t_now; // Number of days in the current month, cast as a Real Real days_this_month; - // - int offset_time_index{0}; }; // TricerTimeState struct TracerData { @@ -93,6 +124,12 @@ struct TracerData { int ncol_{-1}; int nlev_{-1}; int nvars_{-1}; + int nlevs_data; + int ncols_data; + + // + int offset_time_index_{0}; + // We cannot use a std::vector // because we need to access these views from device. // 0: beg 1: end 3: out @@ -109,69 +146,28 @@ struct TracerData { // External forcing file (vertical emission) // Uses altitude instead of pressure to interpolate data - view_1d altitude_int; + view_1d altitude_int_; + // + bool has_altitude_{false}; + // pressure source for ZONAL or FORMULA_PS files + view_2d p_src_; - void allocate_data_views() { + // only for zonal files + view_1d zonal_levs_; + + void allocate_temporal_views() + { + //BEG and OUT data views. EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); EKAT_REQUIRE_MSG(nlev_ != int(-1), "Error! nlevs has not been set. \n"); + EKAT_REQUIRE_MSG(nvars_ != int(-1), "Error! nvars has not been set. \n"); for(int ivar = 0; ivar < nvars_; ++ivar) { data[TracerDataIndex::OUT][ivar] = view_2d("linoz_1_out", ncol_, nlev_); data[TracerDataIndex::BEG][ivar] = view_2d("linoz_1_out", ncol_, nlev_); } - } // allocate_data_views - - void set_file_type(const TracerFileType file_type_in) { - file_type = file_type_in; - } - - void allocate_ps() { - EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); - EKAT_REQUIRE_MSG(file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); - - ps[TracerDataIndex::OUT] = view_1d("ps", ncol_); - ps[TracerDataIndex::BEG] = view_1d("ps", ncol_); - } - - // void set_data_views(view_2d list_of_views[]) { - // for(int ivar = 0; ivar < nvars_; ++ivar) { - // EKAT_REQUIRE_MSG(list_of_views[ivar].data() != 0, - // "Error! Insufficient memory size.\n"); - // data[ivar] = list_of_views[ivar]; - // } - // } - - // void set_data_ps(const view_1d &ps_in) { - // EKAT_REQUIRE_MSG(file_type == FORMULA_PS, - // "Error! file does have the PS variable. \n"); - // ps = ps_in; - // } - // FIXME: The same file is being opened more than twice in the microphysics module.. - void set_hyam_n_hybm(const std::shared_ptr &horiz_remapper, - const std::string &tracer_file_name) { - EKAT_REQUIRE_MSG(file_type == FORMULA_PS, - "Error! file does have the PS variable. \n"); - - // Read in hyam/hybm in start/end data - auto nondim = ekat::units::Units::nondimensional(); - const auto io_grid = horiz_remapper->get_src_grid(); - Field hyam_f(FieldIdentifier("hyam", io_grid->get_vertical_layout(true), - nondim, io_grid->name())); - Field hybm_f(FieldIdentifier("hybm", io_grid->get_vertical_layout(true), - nondim, io_grid->name())); - hyam_f.allocate_view(); - hybm_f.allocate_view(); - AtmosphereInput hvcoord_reader(tracer_file_name, io_grid, {hyam_f, hybm_f}, - true); - hvcoord_reader.read_variables(); - hvcoord_reader.finalize(); - hyam = hyam_f.get_view(); - hybm = hyam_f.get_view(); - } - void allocate_work_vert_inter() - { + // for vertical interpolation using rebin routine if(file_type == FORMULA_PS || file_type == ZONAL) { // we only need work array for FORMULA_PS or ZONAL @@ -179,26 +175,25 @@ struct TracerData { work_vert_inter[ivar] = view_int_1d("allocate_work_vertical_interpolation", ncol_); } + // we use ncremap and python scripts to convert zonal files to ne4pn4 + // grids. + p_src_ = view_2d("pressure_src_invariant", ncol_, nlev_); + } + + if(file_type == TracerFileType::FORMULA_PS) { + ps[TracerDataIndex::OUT] = view_1d("ps", ncol_); + ps[TracerDataIndex::BEG] = view_1d("ps", ncol_); + } + + if(file_type == TracerFileType::ZONAL) { + // we use ncremap and python scripts to convert zonal files to ne4pn4 + // grids. + compute_p_src_zonal_files(zonal_levs_, p_src_); } } }; -// Given a filename, return altitude_int -inline void get_altitude_int( - const std::string &tracer_file_name, - view_1d& altitude_int) { -// in tracer_file_name: NC file name -// out altitude_int: views of int altitude - scorpio::register_file(tracer_file_name, scorpio::Read); - const int nlevs_data = scorpio::get_dimlen(tracer_file_name, "altitude_int"); - view_1d_host altitude_int_host("altitude_int_host", nlevs_data); - scorpio::read_var(tracer_file_name, "altitude_int", altitude_int_host.data()); - scorpio::release_file(tracer_file_name); - altitude_int = view_1d("altitude_int",nlevs_data); - Kokkos::deep_copy(altitude_int, altitude_int_host); -} // set_altitude_int - // Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp // FIXME: I need to convert for loops to Kokkos loops. KOKKOS_INLINE_FUNCTION @@ -315,11 +310,12 @@ inline void get_time_from_ncfile( } dates.push_back(date); } // end itime - scorpio::release_file(file_name); + EKAT_REQUIRE_MSG(cyclical_ymd_index>=0, "Error! Current model time ("+std::to_string(cyclical_ymd)+") is not within "+ "Tracer time period: ["+std::to_string(dates[0])+", "+ "("+std::to_string(dates[nlevs_time-1])+").\n"); + scorpio::release_file(file_name); } inline Real chlorine_loading_advance(const util::TimeStamp &ts, @@ -340,17 +336,16 @@ inline Real chlorine_loading_advance(const util::TimeStamp &ts, return values[index] + delt * (values[index + 1] - values[index]); } -inline std::shared_ptr create_horiz_remapper( - const std::shared_ptr &model_grid, - const std::string &trace_data_file, const std::string &map_file, - const std::vector &var_names, - TracerFileType &tracer_file_type) { - using namespace ShortFieldTagsNames; - +// It reads variables that are not time-dependent and independent of columns (no MPI involved here). +// We also obtain the offset_time_index using a date (cyclical_ymd) as input. +// We initialize a few members of tracer_data. +inline void setup_tracer_data(TracerData &tracer_data, // out + const std::string& trace_data_file, // in + const int cyclical_ymd) //in +{ scorpio::register_file(trace_data_file, scorpio::Read); - // by default, I am assuming a zonal file. - tracer_file_type = ZONAL; + TracerFileType tracer_file_type = ZONAL; int nlevs_data = -1; if(scorpio::has_var(trace_data_file, "lev")) { @@ -363,7 +358,6 @@ inline std::shared_ptr create_horiz_remapper( nlevs_data = scorpio::get_dimlen(trace_data_file, "altitude"); tracer_file_type = VERT_EMISSION; } - EKAT_REQUIRE_MSG( nlevs_data != -1, "Error: The file does not contain either lev or altitude. \n"); @@ -373,29 +367,89 @@ inline std::shared_ptr create_horiz_remapper( // This type of files use model pressure (pmid) for vertical interpolation if(scorpio::has_var(trace_data_file, "PS")) { tracer_file_type = FORMULA_PS; + view_1d_host hyam_h("hyam_h", nlevs_data); + view_1d_host hybm_h("hybm_h", nlevs_data); + + scorpio::read_var(trace_data_file, "hyam", hyam_h.data()); + scorpio::read_var(trace_data_file, "hybm", hybm_h.data()); + view_1d hyam("hyam", nlevs_data); + view_1d hybm("hybm", nlevs_data); + Kokkos::deep_copy(hyam,hyam_h); + Kokkos::deep_copy(hybm,hybm_h); + tracer_data.hyam = hyam; + tracer_data.hybm = hybm; + } + + if (tracer_file_type == ZONAL) + { + view_1d_host levs_h("levs_h", nlevs_data); + view_1d levs("levs", nlevs_data); + scorpio::read_var(trace_data_file, "lev", levs_h.data()); + Kokkos::deep_copy(levs, levs_h); + tracer_data.zonal_levs_ = levs; + } + + if (tracer_file_type == VERT_EMISSION) + { + const int nilevs_data = scorpio::get_dimlen(trace_data_file, "altitude_int"); + view_1d_host altitude_int_host("altitude_int_host", nilevs_data); + view_1d altitude_int = view_1d("altitude_int",nilevs_data); + scorpio::read_var(trace_data_file, "altitude_int", altitude_int_host.data()); + Kokkos::deep_copy(altitude_int, altitude_int_host); + tracer_data.altitude_int_=altitude_int; + } + // time index + { + const int nlevs_time = scorpio::get_dimlen(trace_data_file,"time"); + int cyclical_ymd_index=-1; + for(int itime = 0; itime < nlevs_time; ++itime) { + int date; + scorpio::read_var(trace_data_file, "date", &date, itime); + if(date >= cyclical_ymd) { + cyclical_ymd_index=itime; + break; + } + } // end itime + + EKAT_REQUIRE_MSG(cyclical_ymd_index>=0, + "Error! Current model time ("+std::to_string(cyclical_ymd)+") is not within "+ + "Tracer time period.\n"); + + tracer_data.offset_time_index_ = cyclical_ymd_index; } scorpio::release_file(trace_data_file); + tracer_data.file_type = tracer_file_type; + tracer_data.nlevs_data = nlevs_data; + tracer_data.ncols_data = ncols_data; + tracer_data.has_altitude_ = has_altitude; +} +inline std::shared_ptr create_horiz_remapper( + const std::shared_ptr &model_grid, + const std::string &trace_data_file, const std::string &map_file, + const std::vector &var_names, + TracerData &tracer_data) { + using namespace ShortFieldTagsNames; // We could use model_grid directly if using same num levels, // but since shallow clones are cheap, we may as well do it (less lines of // code) auto horiz_interp_tgt_grid = model_grid->clone("tracer_horiz_interp_tgt_grid", true); - horiz_interp_tgt_grid->reset_num_vertical_lev(nlevs_data); + horiz_interp_tgt_grid->reset_num_vertical_lev(tracer_data.nlevs_data); - if(has_altitude) { + if(tracer_data.file_type == VERT_EMISSION) { horiz_interp_tgt_grid->reset_field_tag_name(LEV, "altitude"); horiz_interp_tgt_grid->reset_field_tag_name(ILEV, "altitude_int"); } const int ncols_model = model_grid->get_num_global_dofs(); std::shared_ptr remapper; - if(ncols_data == ncols_model) { + if(tracer_data.ncols_data == ncols_model) { remapper = std::make_shared( horiz_interp_tgt_grid, IdentityRemapper::SrcAliasTgt); } else { - EKAT_REQUIRE_MSG(ncols_data <= ncols_model, + EKAT_REQUIRE_MSG(tracer_data.ncols_data <= ncols_model, "Error! We do not allow to coarsen spa data to fit the " "model. We only allow\n" " spa data to be at the same or coarser resolution " @@ -425,7 +479,7 @@ inline std::shared_ptr create_horiz_remapper( remapper->register_field_from_tgt(ifield); } // zonal files do not have the PS variable. - if(tracer_file_type == FORMULA_PS) { + if(tracer_data.file_type == FORMULA_PS) { Field ps(FieldIdentifier("PS", layout_2d, nondim, tgt_grid->name())); ps.allocate_view(); remapper->register_field_from_tgt(ps); @@ -514,7 +568,7 @@ inline void update_tracer_timestate( // values // to be assigned. A timestep greater than a month is very unlikely // so we will proceed. - int next_month = time_state.offset_time_index + (time_state.current_month + 1) % 12; + int next_month = data_tracer.offset_time_index_ + (time_state.current_month + 1) % 12; update_tracer_data_from_file(scorpio_reader, next_month, tracer_horiz_interp, data_tracer); } @@ -604,45 +658,6 @@ inline void compute_source_pressure_levels(const view_1d &ps_src, }); } // compute_source_pressure_levels -// Linoz NetCDF files use levs instead of formula_terms. -// This function allocates a view, so we need to do it during initialization. -// Thus, we assume that source pressure is independent of time, -// which is the case for Linoz files (zonal file). -inline void compute_p_src_zonal_files(const std::string &tracer_file_name, - const view_2d &p_src) { - EKAT_REQUIRE_MSG(p_src.data() != 0, - "Error: p_src has not been allocated. \n"); - // Read in levs in start/end data - // FIXME: units are mbar; how can I get units using scorpio interface - auto nondim = ekat::units::Units::nondimensional(); - scorpio::register_file(tracer_file_name, scorpio::Read); - const int nlevs_data = scorpio::get_dimlen(tracer_file_name, "lev"); - view_1d_host levs_h("levs_h", nlevs_data); - scorpio::read_var(tracer_file_name, "lev", levs_h.data()); - scorpio::release_file(tracer_file_name); - view_1d levs("levs", nlevs_data); - Kokkos::deep_copy(levs, levs_h); - - const int ncol = p_src.extent(0); - EKAT_REQUIRE_MSG( - p_src.extent(1) == nlevs_data, - "Error: p_src has a different number of levels than the source data. \n"); - - const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); - const int pi = haero::Constants::pi; - Kokkos::parallel_for( - "pressure_computation", policy_pressure, KOKKOS_LAMBDA(const Team &team) { - const int icol = team.league_rank(); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), - [&](const Int &kk) { - // mbar->pascals - // FIXME: Does EAMxx have a better method to - // convert units?" - p_src(icol, kk) = levs(kk) * 100; - }); - }); - Kokkos::fence(); -} inline void perform_vertical_interpolation(const view_2d &p_src_c, const const_view_2d &p_tgt_c, @@ -650,7 +665,7 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, const view_2d output[]) { // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_ == output[0].extent(0)); + EKAT_REQUIRE(input.ncol_ == int(output[0].extent(0))); const int ncol = input.ncol_; const int levsiz =input.nlev_; const int pver = mam4::nlev; @@ -758,9 +773,11 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, inline void advance_tracer_data( std::shared_ptr &scorpio_reader, - AbstractRemapper &tracer_horiz_interp, const util::TimeStamp &ts, - TracerTimeState &time_state, TracerData &data_tracer, - const view_2d &p_src, const const_view_2d &p_tgt, + AbstractRemapper &tracer_horiz_interp, + const util::TimeStamp &ts, + TracerTimeState &time_state, + TracerData &data_tracer, + const const_view_2d &p_tgt, const const_view_2d &zi_tgt, const view_2d output[]) { /* Update the TracerTimeState to reflect the current time, note the addition @@ -777,16 +794,16 @@ inline void advance_tracer_data( if(data_tracer.file_type == FORMULA_PS) { // Step 2. Compute source pressure levels const auto ps = data_tracer.ps[TracerDataIndex::OUT]; - compute_source_pressure_levels(ps, p_src, + compute_source_pressure_levels(ps, data_tracer.p_src_, data_tracer.hyam, data_tracer.hybm); } // Step 3. Perform vertical interpolation if(data_tracer.file_type == FORMULA_PS || data_tracer.file_type == ZONAL) { - perform_vertical_interpolation(p_src, p_tgt, data_tracer, output); + perform_vertical_interpolation(data_tracer.p_src_, p_tgt, data_tracer, output); } else if(data_tracer.file_type == VERT_EMISSION) { - perform_vertical_interpolation(data_tracer.altitude_int, zi_tgt, data_tracer, output); + perform_vertical_interpolation(data_tracer.altitude_int_, zi_tgt, data_tracer, output); } } // advance_tracer_data From 3752e83ebe0dd3dbc06d38c1d3914b1f0f05023a Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 4 Sep 2024 11:00:36 -0600 Subject: [PATCH 21/31] Using mam4::utils::extract_stateq_from_prognostics to copy data from prog to state_q. shr_orb_cosz_c2f is fortran routine. Save Fields from Tracer Reader to Field Manager Modifying name of preprocessor definition and updating comments. Cleans up gas phase chemisrty code- code compiles file with all the changes Code runs fine with commenting out ext forc and linoz Gas phase chemistry done- reverted some commented out code Using mam4::vertical_interpolation::vert_interp. Using mam4::vertical_interpolation::rebin --- ...mxx_mam_microphysics_process_interface.cpp | 570 +-- ...mxx_mam_microphysics_process_interface.hpp | 5 + .../mam/impl/compute_o3_column_density.cpp | 37 +- .../physics/mam/impl/gas_phase_chemistry.cpp | 431 +- .../src/physics/mam/impl/helper_micro.hpp | 327 +- .../src/physics/mam/impl/mam4_amicphys.cpp | 3596 ++++++++++------- .../eamxx/src/physics/mam/mam_coupling.hpp | 193 +- .../mam/aero_microphys/CMakeLists.txt | 2 +- .../mam/aero_microphys/output.yaml | 7 +- externals/mam4xx | 2 +- 10 files changed, 2683 insertions(+), 2487 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 00f987471a6..2ca410611fe 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -22,6 +22,18 @@ #include #include +/* When the preprocessor definition ENABLE_OUTPUT_TRACER_FIELDS is +enabled, the fields oxi_fields, linoz_fields, and +vertical_emission_fields will be saved. +These fields, which are the output of the tracer reader, + have been stored in the FM for evaluation purposes. + There are 33 fields (ncolxnlev) in total. + Therefore, it is recommended to disable + the ENABLE_OUTPUT_TRACER_FIELDS preprocessor + definition once the evaluation of the microphysics interface + is completed. */ +#define ENABLE_OUTPUT_TRACER_FIELDS + namespace scream { MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, @@ -57,15 +69,6 @@ void MAMMicrophysics::set_defaults_() { config_.amicphys.do_newnuc = true; config_.amicphys.do_coag = true; - config_.amicphys.nucleation = {}; - config_.amicphys.nucleation.dens_so4a_host = 1770.0; - config_.amicphys.nucleation.mw_so4a_host = 115.0; - config_.amicphys.nucleation.newnuc_method_user_choice = 2; - config_.amicphys.nucleation.pbl_nuc_wang2008_user_choice = 1; - config_.amicphys.nucleation.adjust_factor_pbl_ratenucl = 1.0; - config_.amicphys.nucleation.accom_coef_h2so4 = 1.0; - config_.amicphys.nucleation.newnuc_adjust_factor_dnaitdt = 1.0; - // these parameters guide the coupling between parameterizations // NOTE: mam4xx was ported with these parameters fixed, so it's probably not // NOTE: safe to change these without code modifications. @@ -129,21 +132,18 @@ void MAMMicrophysics::set_grids( // At interfaces FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; - const int nmodes = mam4::AeroConfig::num_modes(); // Number of modes + const int nmodes = mam4::AeroConfig::num_modes(); // Number of modes // layout for 3D (ncol, nmodes, nlevs) FieldLayout scalar3d_mid_nmodes = grid_->get_3d_vector_layout(true, nmodes, "nmodes"); static constexpr auto m3 = m * m * m; - // Aerosol dry particle diameter [m] + // Aerosol dry particle diameter [m] add_field("dgncur_a", scalar3d_mid_nmodes, m, grid_name); // Wet aerosol density [kg/m3] add_field("wetdens", scalar3d_mid_nmodes, kg / m3, grid_name); - // Aerosol water [kg/kg] - add_field("qaerwat", scalar3d_mid_nmodes, kg / kg, grid_name); - // Wet Required diameter [m] add_field("dgnumwet", scalar3d_mid_nmodes, m, grid_name); @@ -237,8 +237,8 @@ void MAMMicrophysics::set_grids( // in format YYYYMMDD int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); - scream::mam_coupling::setup_tracer_data(linoz_data_, - linoz_file_name_, linoz_cyclical_ymd); + scream::mam_coupling::setup_tracer_data(linoz_data_, linoz_file_name_, + linoz_cyclical_ymd); LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( grid_, linoz_file_name_, spa_map_file, var_names, linoz_data_); LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader( @@ -251,27 +251,42 @@ void MAMMicrophysics::set_grids( const int num_levs_io_linoz = io_grid_linoz ->get_num_vertical_levels(); // Number of levels per column - const int nvars = int(var_names.size());; + const int nvars = int(var_names.size()); linoz_data_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_.allocate_temporal_views(); + +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + FieldLayout scalar3d_mid_linoz = + grid_->get_3d_vector_layout(true, nvars, "nlinoz_fields"); + // Note: Using nondim because the linoz fields have difference type of units + /* linoz_o3_clim : ozone (climatology) [vmr] + linoz_t_clim ! temperature (climatology) [K] + linoz_o3col_clim Column O3 above box (climatology) [Dobson Units or DU] + linoz_PmL_clim P minus L (climatology) [vmr/s] + linoz_dPmL_dO3 Sensitivity of P minus L to O3 [1/s] + linoz_dPmL_dT Sensitivity of P minus L to T [K] + linoz_dPmL_dO3col Sensitivity of P minus L to overhead O3 column [vmr/DU] + linoz_cariolle_psc Cariolle parameter for PSC loss of ozone [1/s] */ + add_field("linoz_fields", scalar3d_mid_linoz, nondim, grid_name); +#endif } { oxid_file_name_ = m_params.get("mam4_oxid_file_name"); std::string spa_map_file = ""; - //NOTE: order matches mam4xx: + // NOTE: order matches mam4xx: std::vector var_names{"O3", "OH", "NO3", "HO2"}; // //in format YYYYMMDD int oxid_ymd = m_params.get("mam4_oxid_ymd"); - scream::mam_coupling::setup_tracer_data(tracer_data_, - oxid_file_name_, oxid_ymd); + scream::mam_coupling::setup_tracer_data(tracer_data_, oxid_file_name_, + oxid_ymd); TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( grid_, oxid_file_name_, spa_map_file, var_names, tracer_data_); TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader( TracerHorizInterp_, oxid_file_name_); - const int nvars = int(var_names.size());; + const int nvars = int(var_names.size()); const auto io_grid = TracerHorizInterp_->get_src_grid(); const int num_cols_io = io_grid->get_num_local_dofs(); // Number of columns on this rank @@ -284,46 +299,56 @@ void MAMMicrophysics::set_grids( cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); } +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + FieldLayout scalar3d_mid_oxi = + grid_->get_3d_vector_layout(true, nvars, "noxid_fields"); + // NOTE: Assuming nondim for units. + add_field("oxi_fields", scalar3d_mid_oxi, nondim, grid_name); +#endif } { // FIXME: I will need to add this file per forcing file. std::string spa_map_file = ""; // NOTE: order of forcing species is important. - // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 ','pom_a4 ','bc_a4 ', - // 'num_a1 ','num_a2 ','num_a4 ','SOAG ' } + // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 + // ','pom_a4 ','bc_a4 ', 'num_a1 ','num_a2 + // ','num_a4 ','SOAG ' } // This order corresponds to files in namelist e3smv2 - extfrc_lst_=std::vector({"so2","so4_a1","so4_a2","pom_a4","bc_a4", - "num_a1","num_a2","num_a4","soag"}); + extfrc_lst_ = + std::vector({"so2", "so4_a1", "so4_a2", "pom_a4", "bc_a4", + "num_a1", "num_a2", "num_a4", "soag"}); - for (const auto& var_name : extfrc_lst_) { - std::string item_name= "mam4_"+var_name+"_verti_emiss_file_name"; - const auto file_name = m_params.get(item_name); + for(const auto &var_name : extfrc_lst_) { + std::string item_name = "mam4_" + var_name + "_verti_emiss_file_name"; + const auto file_name = m_params.get(item_name); vert_emis_file_name_[var_name] = file_name; } - vert_emis_var_names_["so2"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; - vert_emis_var_names_["so4_a1"] = {"BB","ENE_ELEV", "IND_ELEV", "contvolc"}; - vert_emis_var_names_["so4_a2"] = { "contvolc"}; + vert_emis_var_names_["so2"] = {"BB", "ENE_ELEV", "IND_ELEV", "contvolc"}; + vert_emis_var_names_["so4_a1"] = {"BB", "ENE_ELEV", "IND_ELEV", "contvolc"}; + vert_emis_var_names_["so4_a2"] = {"contvolc"}; vert_emis_var_names_["pom_a4"] = {"BB"}; - vert_emis_var_names_["bc_a4"] = {"BB"}; - vert_emis_var_names_["num_a1"] = {"num_a1_SO4_ELEV_BB","num_a1_SO4_ELEV_ENE", "num_a1_SO4_ELEV_IND", "num_a1_SO4_ELEV_contvolc"}; + vert_emis_var_names_["bc_a4"] = {"BB"}; + vert_emis_var_names_["num_a1"] = { + "num_a1_SO4_ELEV_BB", "num_a1_SO4_ELEV_ENE", "num_a1_SO4_ELEV_IND", + "num_a1_SO4_ELEV_contvolc"}; vert_emis_var_names_["num_a2"] = {"num_a2_SO4_ELEV_contvolc"}; // num_a4 // FIXME: why the sectors in this files are num_a1; // I guess this should be num_a4? Is this a bug in the orginal nc files? - vert_emis_var_names_["num_a4"] = {"num_a1_BC_ELEV_BB", "num_a1_POM_ELEV_BB"}; - vert_emis_var_names_["soag"] = {"SOAbb_src","SOAbg_src", "SOAff_src"}; - + vert_emis_var_names_["num_a4"] = {"num_a1_BC_ELEV_BB", + "num_a1_POM_ELEV_BB"}; + vert_emis_var_names_["soag"] = {"SOAbb_src", "SOAbg_src", "SOAff_src"}; int verti_emiss_cyclical_ymd = m_params.get("verti_emiss_ymd"); - for (const auto& var_name : extfrc_lst_) { + for(const auto &var_name : extfrc_lst_) { const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; scream::mam_coupling::TracerData data_tracer; - scream::mam_coupling::setup_tracer_data(data_tracer, - file_name, verti_emiss_cyclical_ymd); + scream::mam_coupling::setup_tracer_data(data_tracer, file_name, + verti_emiss_cyclical_ymd); auto hor_rem = scream::mam_coupling::create_horiz_remapper( grid_, file_name, spa_map_file, var_names, data_tracer); auto file_reader = @@ -331,19 +356,19 @@ void MAMMicrophysics::set_grids( VertEmissionsHorizInterp_.push_back(hor_rem); VertEmissionsDataReader_.push_back(file_reader); vert_emis_data_.push_back(data_tracer); - }// var_name vert emissions - int i=0; - int offset_emis_ver=0; - for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { - const auto var_name = *it; + } // var_name vert emissions + int i = 0; + int offset_emis_ver = 0; + for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + const auto var_name = *it; const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; - const int nvars = int(var_names.size()); + const int nvars = int(var_names.size()); forcings_[i].nsectors = nvars; // I am assuming the order of species in extfrc_lst_. // Indexing in mam4xx is fortran. - forcings_[i].frc_ndx = i+1; + forcings_[i].frc_ndx = i + 1; const auto io_grid_emis = VertEmissionsHorizInterp_[i]->get_src_grid(); const int num_cols_io_emis = io_grid_emis->get_num_local_dofs(); // Number of columns on this rank @@ -352,19 +377,27 @@ void MAMMicrophysics::set_grids( ->get_num_vertical_levels(); // Number of levels per column vert_emis_data_[i].init(num_cols_io_emis, num_levs_io_emis, nvars); vert_emis_data_[i].allocate_temporal_views(); - forcings_[i].file_alt_data=vert_emis_data_[i].has_altitude_; - for (int isp = 0; isp < nvars; ++isp) - { + forcings_[i].file_alt_data = vert_emis_data_[i].has_altitude_; + for(int isp = 0; isp < nvars; ++isp) { EKAT_REQUIRE_MSG( - offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), - "Error! Number of fields is bigger than MAX_NUM_VERT_EMISSION_FIELDS. Increase the MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); - forcings_[i].offset=offset_emis_ver; - vert_emis_output_[isp+offset_emis_ver] = - view_2d("vert_emis_output_", ncol_, nlev_); + offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), + "Error! Number of fields is bigger than " + "MAX_NUM_VERT_EMISSION_FIELDS. Increase the " + "MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); + forcings_[i].offset = offset_emis_ver; + vert_emis_output_[isp + offset_emis_ver] = + view_2d("vert_emis_output_", ncol_, nlev_); } - offset_emis_ver+=nvars; + offset_emis_ver += nvars; } // end i +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + FieldLayout scalar3d_mid_emis_ver = grid_->get_3d_vector_layout( + true, offset_emis_ver, "nvertical_emission"); + // NOTE: Assuming nondim for units. + add_field("vertical_emission_fields", scalar3d_mid_emis_ver, + nondim, grid_name); +#endif } } @@ -570,29 +603,32 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { const int curr_month = timestamp().get_month() - 1; // 0-based scream::mam_coupling::update_tracer_data_from_file( - LinozDataReader_, - curr_month, - *LinozHorizInterp_, - linoz_data_); + LinozDataReader_, curr_month, *LinozHorizInterp_, linoz_data_); scream::mam_coupling::update_tracer_data_from_file( - TracerDataReader_, curr_month, *TracerHorizInterp_, - tracer_data_); + TracerDataReader_, curr_month, *TracerHorizInterp_, tracer_data_); - int i=0; - for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + int i = 0; + for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { scream::mam_coupling::update_tracer_data_from_file( - VertEmissionsDataReader_[i], curr_month, - *VertEmissionsHorizInterp_[i], vert_emis_data_[i]); + VertEmissionsDataReader_[i], curr_month, *VertEmissionsHorizInterp_[i], + vert_emis_data_[i]); } invariants_ = view_3d("invarians", ncol_, nlev_, mam4::gas_chemistry::nfs); - constexpr int extcnt = - mam4::gas_chemistry::extcnt; - extfrc_=view_3d("extfrc_", ncol_, nlev_, extcnt); + constexpr int extcnt = mam4::gas_chemistry::extcnt; + extfrc_ = view_3d("extfrc_", ncol_, nlev_, extcnt); + + // + acos_cosine_zenith_host_ = view_1d_host("host_acos(cosine_zenith)", ncol_); + acos_cosine_zenith_ = view_1d("device_acos(cosine_zenith)", ncol_); } +// ================================================================ +// RUN_IMPL +// ================================================================ + void MAMMicrophysics::run_impl(const double dt) { const auto scan_policy = ekat::ExeSpaceUtils< KT::ExeSpace>::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); @@ -603,18 +639,12 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::parallel_for("preprocess", scan_policy, preprocess_); Kokkos::fence(); - const auto wet_geometric_mean_diameter_i = get_field_in("dgnumwet").get_view(); - const auto dry_geometric_mean_diameter_i = get_field_in("dgncur_a").get_view(); - const auto qaerwat = get_field_in("qaerwat").get_view(); + const auto wet_geometric_mean_diameter_i = + get_field_in("dgnumwet").get_view(); + const auto dry_geometric_mean_diameter_i = + get_field_in("dgncur_a").get_view(); const auto wetdens = get_field_in("wetdens").get_view(); - // reset internal WSM variables - // workspace_mgr_.reset_internals(); - - // NOTE: nothing depends on simulation time (yet), so we can just use zero for - // now - // double t = 0.0; - // climatology data for linear stratospheric chemistry auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] auto linoz_o3col_clim = @@ -640,7 +670,6 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_output[5] = linoz_dPmL_dT; linoz_output[6] = linoz_dPmL_dO3col; linoz_output[7] = linoz_cariolle_pscs; - // it's a bit wasteful to store this for all columns, but simpler from an // allocation perspective auto o3_col_dens = buffer_.scratch[8]; @@ -656,53 +685,86 @@ void MAMMicrophysics::run_impl(const double dt) { trace_time_state_.t_now = ts.frac_of_year_in_days(); scream::mam_coupling::advance_tracer_data( TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, - tracer_data_, - dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); + tracer_data_, dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); + Kokkos::fence(); + +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + const auto oxi_fields_outputs = + get_field_out("oxi_fields").get_view(); + for(int ifield = 0; ifield < int(oxi_fields_outputs.extent(1)); ++ifield) { + const auto field_at_i = Kokkos::subview(oxi_fields_outputs, Kokkos::ALL(), + ifield, Kokkos::ALL()); + Kokkos::deep_copy(field_at_i, cnst_offline_[ifield]); + } +#endif scream::mam_coupling::advance_tracer_data( - LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, - linoz_data_, + LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, linoz_data_, dry_atm_.p_mid, dry_atm_.z_iface, linoz_output); + Kokkos::fence(); + +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + const auto linoz_fields_outputs = + get_field_out("linoz_fields").get_view(); + for(int ifield = 0; ifield < int(linoz_fields_outputs.extent(1)); ++ifield) { + const auto field_at_i = Kokkos::subview(linoz_fields_outputs, Kokkos::ALL(), + ifield, Kokkos::ALL()); + Kokkos::deep_copy(field_at_i, linoz_output[ifield]); + } +#endif vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); - int i=0; - for (auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { - const auto var_name = *it; + int i = 0; + for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + const auto var_name = *it; const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; - const int nsectors = int(var_names.size()); + const int nsectors = int(var_names.size()); view_2d vert_emis_output[nsectors]; - for (int isp = 0; isp < nsectors; ++isp) - { - vert_emis_output[isp]= vert_emis_output_[isp+forcings_[i].offset]; + for(int isp = 0; isp < nsectors; ++isp) { + vert_emis_output[isp] = vert_emis_output_[isp + forcings_[i].offset]; } scream::mam_coupling::advance_tracer_data( VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, - vert_emiss_time_state_, - vert_emis_data_[i], dry_atm_.p_mid, + vert_emiss_time_state_, vert_emis_data_[i], dry_atm_.p_mid, dry_atm_.z_iface, vert_emis_output); + Kokkos::fence(); } - const_view_1d &col_latitudes = col_latitudes_; - const_view_1d &col_longitudes = col_longitudes_; - const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; +#if defined(ENABLE_OUTPUT_TRACER_FIELDS) + const auto ver_emiss_fields_outputs = + get_field_out("vertical_emission_fields").get_view(); + for(int ifield = 0; ifield < int(ver_emiss_fields_outputs.extent(1)); + ++ifield) { + const auto field_at_i = Kokkos::subview( + ver_emiss_fields_outputs, Kokkos::ALL(), ifield, Kokkos::ALL()); + Kokkos::deep_copy(field_at_i, vert_emis_output_[ifield]); + } +#endif + const_view_1d &col_latitudes = col_latitudes_; + // const_view_1d &col_longitudes = col_longitudes_; + const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; + mam_coupling::DryAtmosphere &dry_atm = dry_atm_; + mam_coupling::AerosolState &dry_aero = dry_aero_; + mam_coupling::AerosolState &wet_aero = wet_aero_; - mam_coupling::DryAtmosphere &dry_atm = dry_atm_; - mam_coupling::AerosolState &dry_aero = dry_aero_; mam4::mo_photo::PhotoTableData &photo_table = photo_table_; const int nlev = nlev_; const Config &config = config_; const auto &step = step_; - const auto &work_photo_table = work_photo_table_; - const auto &photo_rates = photo_rates_; + const auto &work_photo_table = work_photo_table_; + const auto &photo_rates = photo_rates_; const auto &invariants = invariants_; const auto &cnst_offline = cnst_offline_; // Compute orbital parameters; these are used both for computing // the solar zenith angle. + // Note: We are following the RRTMGP EAMxx interface to compute the zenith + // angle. This operation is performed on the host because the routine + // shr_orb_cosz_c2f has not been ported to C++. auto ts2 = timestamp(); double obliqr, lambm0, mvelpp; auto orbital_year = m_orbital_year; @@ -718,34 +780,61 @@ void MAMMicrophysics::run_impl(const double dt) { // compute orbital parameters based on current year orbital_year = ts2.get_year(); } - shr_orb_params_c2f(&orbital_year, &eccen, &obliq, &mvelp, &obliqr, &lambm0, - &mvelpp); + shr_orb_params_c2f(&orbital_year, // in + &eccen, &obliq, &mvelp, &obliqr, &lambm0, &mvelpp); // out // Use the orbital parameters to calculate the solar declination and // eccentricity factor Real delta, eccf; auto calday = ts2.frac_of_year_in_days() + 1; // Want day + fraction; calday 1 == Jan 1 0Z - shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, obliqr, &delta, &eccf); + shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, obliqr, // in + &delta, &eccf); // out + { + const auto col_latitudes_host = + grid_->get_geometry_data("lat").get_view(); + const auto col_longitudes_host = + grid_->get_geometry_data("lon").get_view(); + // get a host copy of lat/lon + // Determine the cosine zenith angle + // NOTE: Since we are bridging to F90 arrays this must be done on HOST and + // then + // deep copied to a device view. + + // Now use solar declination to calculate zenith angle for all points + for(int i = 0; i < ncol_; i++) { + Real lat = + col_latitudes_host(i) * M_PI / 180.0; // Convert lat/lon to radians + Real lon = col_longitudes_host(i) * M_PI / 180.0; + // what's the aerosol microphys frequency? + Real temp = shr_orb_cosz_c2f(calday, lat, lon, delta, dt); + acos_cosine_zenith_host_(i) = acos(temp); + } + Kokkos::deep_copy(acos_cosine_zenith_, acos_cosine_zenith_host_); + } + const auto zenith_angle = acos_cosine_zenith_; + constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int gas_pcnst = mam_coupling::gas_pcnst(); constexpr int nqtendbb = mam_coupling::nqtendbb(); - constexpr int pcnst = mam4::pcnst; + constexpr int pcnst = mam4::pcnst; const auto vert_emis_output = vert_emis_output_; - const auto extfrc = extfrc_; - const auto forcings = forcings_; - constexpr int extcnt = mam4::gas_chemistry::extcnt; + const auto extfrc = extfrc_; + const auto forcings = forcings_; + constexpr int extcnt = mam4::gas_chemistry::extcnt; - // FIXME: remove this hard-code value const int offset_aerosol = mam4::utils::gasses_start_ind(); Real adv_mass_kg_per_moles[gas_pcnst]; - // NOTE: Making copies of clsmap_4 and permute_4 to fix undefined arrays on the device. + // NOTE: Making copies of clsmap_4 and permute_4 to fix undefined arrays on + // the device. int clsmap_4[gas_pcnst], permute_4[gas_pcnst]; - for(int i = 0; i < gas_pcnst; ++i) - { - adv_mass_kg_per_moles[i] = mam4::gas_chemistry::adv_mass[i]/1e3; - clsmap_4[i]=mam4::gas_chemistry::clsmap_4[i]; - permute_4[i]=mam4::gas_chemistry::permute_4[i]; + for(int i = 0; i < gas_pcnst; ++i) { + // NOTE: state_q is kg/kg-dry-air; adv_mass is in g/mole. + // Convert adv_mass to kg/mole as vmr_from_mmr function uses + // molec_weight_dry_air with kg/mole units + adv_mass_kg_per_moles[i] = mam4::gas_chemistry::adv_mass[i] / 1e3; + clsmap_4[i] = mam4::gas_chemistry::clsmap_4[i]; + permute_4[i] = mam4::gas_chemistry::permute_4[i]; } // loop over atmosphere columns and compute aerosol microphyscs @@ -753,13 +842,13 @@ void MAMMicrophysics::run_impl(const double dt) { policy, KOKKOS_LAMBDA(const ThreadTeam &team) { const int icol = team.league_rank(); // column index - Real col_lat = col_latitudes(icol); // column latitude (degrees?) - Real col_lon = col_longitudes(icol); // column longitude + Real col_lat = col_latitudes(icol); // column latitude (degrees?) + // Real col_lon = col_longitudes(icol); // column longitude Real rlats = col_lat * M_PI / 180.0; // convert column latitude to radians - Real rlons = - col_lon * M_PI / 180.0; // convert column longitude to radians + // Real rlons = + // col_lon * M_PI / 180.0; // convert column longitude to radians // fetch column-specific atmosphere state data auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); @@ -770,12 +859,7 @@ void MAMMicrophysics::run_impl(const double dt) { ekat::subview(wet_geometric_mean_diameter_i, icol); auto dry_diameter_icol = ekat::subview(dry_geometric_mean_diameter_i, icol); - auto qaerwat_icol = ekat::subview(qaerwat, icol); - auto wetdens_icol = ekat::subview(wetdens, icol); - - - // set surface state data - haero::Surface sfc{}; + auto wetdens_icol = ekat::subview(wetdens, icol); // fetch column-specific subviews into aerosol prognostics mam4::Prognostics progs = @@ -785,136 +869,127 @@ void MAMMicrophysics::run_impl(const double dt) { mam4::Diagnostics diags(nlev); const auto invariants_icol = ekat::subview(invariants, icol); mam4::mo_setext::Forcing forcings_in[extcnt]; - for (int i = 0; i < extcnt; ++i) - { - int nsectors = forcings[i].nsectors; - int frc_ndx = forcings[i].frc_ndx; - auto file_alt_data= forcings[i].file_alt_data; - - forcings_in[i].nsectors=nsectors; - forcings_in[i].frc_ndx=frc_ndx; + + for(int i = 0; i < extcnt; ++i) { + int nsectors = forcings[i].nsectors; + int frc_ndx = forcings[i].frc_ndx; + auto file_alt_data = forcings[i].file_alt_data; + + forcings_in[i].nsectors = nsectors; + forcings_in[i].frc_ndx = frc_ndx; // We may need to move this line where we read files. - forcings_in[i].file_alt_data=file_alt_data; - for (int isec = 0; isec < forcings[i].nsectors; ++isec) - { - const auto field = vert_emis_output[isec+forcings[i].offset]; - forcings_in[i].fields_data[isec]=ekat::subview(field, icol); + forcings_in[i].file_alt_data = file_alt_data; + for(int isec = 0; isec < forcings[i].nsectors; ++isec) { + const auto field = vert_emis_output[isec + forcings[i].offset]; + forcings_in[i].fields_data[isec] = ekat::subview(field, icol); } - } + } // extcnt for loop + const auto extfrc_icol = ekat::subview(extfrc, icol); mam4::mo_setext::extfrc_set(forcings_in, extfrc_icol); view_1d cnst_offline_icol[mam4::mo_setinv::num_tracer_cnst]; - for (int i = 0; i < mam4::mo_setinv::num_tracer_cnst; ++i) { - cnst_offline_icol[i] = ekat::subview(cnst_offline[i],icol); + for(int i = 0; i < mam4::mo_setinv::num_tracer_cnst; ++i) { + cnst_offline_icol[i] = ekat::subview(cnst_offline[i], icol); } mam4::mo_setinv::setinv(team, invariants_icol, atm.temperature, - atm.vapor_mixing_ratio, - cnst_offline_icol, atm.pressure); + atm.vapor_mixing_ratio, cnst_offline_icol, + atm.pressure); // calculate o3 column densities (first component of col_dens in Fortran // code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); impl::compute_o3_column_density(team, atm, progs, invariants_icol, - o3_col_dens_i); + adv_mass_kg_per_moles, o3_col_dens_i); // set up photolysis work arrays for this column. mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; - const auto& work_photo_table_icol = ekat::subview(work_photo_table, - icol); - // set work view using 1D photo_work_arrays_icol - mam4::mo_photo::set_photo_table_work_arrays(photo_table, - work_photo_table_icol, - photo_work_arrays_icol); - - // ... look up photolysis rates from our table - // NOTE: the table interpolation operates on an entire column of data, - // so we NOTE: must do it before dispatching to individual vertical - // levels - Real zenith_angle = - shr_orb_cosz_c2f(calday, rlats, rlons, delta, - dt); // what's the aerosol microphys frequency? - zenith_angle = acos(zenith_angle); + const auto &work_photo_table_icol = + ekat::subview(work_photo_table, icol); - Real surf_albedo = d_sfc_alb_dir_vis(icol); + // set work view using 1D photo_work_arrays_icol + // Note: We are not allocating views here. + mam4::mo_photo::set_photo_table_work_arrays( + photo_table, work_photo_table_icol, photo_work_arrays_icol); const auto &photo_rates_icol = ekat::subview(photo_rates, icol); - - mam4::mo_photo::table_photo(photo_rates_icol, atm.pressure, - atm.hydrostatic_dp, - atm.temperature, o3_col_dens_i, zenith_angle, surf_albedo, - atm.liquid_mixing_ratio, atm.cloud_fraction, eccf, photo_table, - photo_work_arrays_icol); + mam4::mo_photo::table_photo( + photo_rates_icol, atm.pressure, atm.hydrostatic_dp, atm.temperature, + o3_col_dens_i, zenith_angle(icol), d_sfc_alb_dir_vis(icol), + atm.liquid_mixing_ratio, atm.cloud_fraction, eccf, photo_table, + photo_work_arrays_icol); // compute aerosol microphysics on each vertical level within this // column Kokkos::parallel_for( - Kokkos::TeamThreadRange(team, nlev), [&](const int k) { + Kokkos::TeamThreadRange(team, nlev), [&](const int kk) { // extract atm state variables (input) - Real temp = atm.temperature(k); - Real pmid = atm.pressure(k); - Real pdel = atm.hydrostatic_dp(k); - Real zm = atm.height(k); - Real zi = z_iface(k); + Real temp = atm.temperature(kk); + Real pmid = atm.pressure(kk); + Real pdel = atm.hydrostatic_dp(kk); + Real zm = atm.height(kk); + Real zi = z_iface(kk); Real pblh = atm.planetary_boundary_layer_height; - Real qv = atm.vapor_mixing_ratio(k); - Real cldfrac = atm.cloud_fraction(k); - Real lwc = atm.liquid_mixing_ratio(k); - Real cldnum = atm.cloud_liquid_number_mixing_ratio(k); + Real qv = atm.vapor_mixing_ratio(kk); + Real cldfrac = atm.cloud_fraction(kk); + Real lwc = atm.liquid_mixing_ratio(kk); + Real cldnum = atm.cloud_liquid_number_mixing_ratio(kk); // extract aerosol state variables into "working arrays" (mass // mixing ratios) (in EAM, this is done in the gas_phase_chemdr // subroutine defined within // mozart/mo_gas_phase_chemdr.F90) - Real q[gas_pcnst] = {}; - Real qqcw[gas_pcnst] = {}; - // mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, - // qqcw); Real state_q[pcnst] = {}; - Real qqcw_long[pcnst] = {}; - mam4::utils::extract_stateq_from_prognostics(progs,atm, state_q, k); - - mam4::utils::extract_qqcw_from_prognostics(progs,qqcw_long,k); - - for (int i = offset_aerosol; i < pcnst; ++i) { - q[i-offset_aerosol] =state_q[i]; - qqcw[i-offset_aerosol] =qqcw_long[i]; + Real qqcw_pcnst[pcnst] = {}; + // output (state_q) + mam4::utils::extract_stateq_from_prognostics(progs, atm, state_q, + kk); + // output (qqcw_pcnst) + mam4::utils::extract_qqcw_from_prognostics(progs, qqcw_pcnst, kk); + + Real qq[gas_pcnst] = {}; + Real qqcw[gas_pcnst] = {}; + for(int i = offset_aerosol; i < pcnst; ++i) { + qq[i - offset_aerosol] = state_q[i]; + qqcw[i - offset_aerosol] = qqcw_pcnst[i]; } // convert mass mixing ratios to volume mixing ratios (VMR), // equivalent to tracer mixing ratios (TMR)) Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - // CHECK: convert_work_arrays_to_vmr and mmr2vmr should produce the same ouputs - // However, in mmr2vmr we do not iterate to get species value. - // mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); - mam_coupling::mmr2vmr(q,adv_mass_kg_per_moles, vmr); - mam_coupling::mmr2vmr(qqcw,adv_mass_kg_per_moles,vmrcw); - - // aerosol/gas species tendencies (output) - Real vmr_tendbb[gas_pcnst][nqtendbb] = {}; - Real vmrcw_tendbb[gas_pcnst][nqtendbb] = {}; - - // create work array copies to retain "pre-chemistry" values - Real vmr_pregaschem[gas_pcnst] = {}; - Real vmr_precldchem[gas_pcnst] = {}; - Real vmrcw_precldchem[gas_pcnst] = {}; - for(int i = 0; i < gas_pcnst; ++i) { - vmr_pregaschem[i] = vmr[i]; - vmr_precldchem[i] = vmr[i]; - vmrcw_precldchem[i] = vmrcw[i]; - } + // output (vmr) + mam_coupling::mmr2vmr(qq, adv_mass_kg_per_moles, vmr); + // output (vmrcw) + mam_coupling::mmr2vmr(qqcw, adv_mass_kg_per_moles, vmrcw); //--------------------- // Gas Phase Chemistry //--------------------- // - const auto& extfrc_k = ekat::subview(extfrc_icol,k); - const auto& invariants_k = ekat::subview(invariants_icol,k); - const auto& photo_rates_k = ekat::subview(photo_rates_icol,k); - impl::gas_phase_chemistry(zm, zi, phis, temp, pmid, pdel, dt, - photo_rates_k.data(), extfrc_k.data(), - invariants_k.data(), clsmap_4, permute_4, vmr); + const auto &extfrc_k = ekat::subview(extfrc_icol, kk); + const auto &invariants_k = ekat::subview(invariants_icol, kk); + const auto &photo_rates_k = ekat::subview(photo_rates_icol, kk); + + // vmr0 stores mixing ratios before chemistry changes the mixing + // ratios + Real vmr0[gas_pcnst] = {}; + impl::gas_phase_chemistry( + // in + temp, dt, photo_rates_k.data(), extfrc_k.data(), + invariants_k.data(), clsmap_4, permute_4, + // out + vmr, vmr0); + + // create work array copies to retain "pre-chemistry (aqueous)" + // values + Real vmr_pregas[gas_pcnst] = {}; + Real vmr_precld[gas_pcnst] = {}; + for(int i = 0; i < gas_pcnst; ++i) { + vmr_pregas[i] = vmr[i]; + vmr_precld[i] = vmrcw[i]; + } + //---------------------- // Aerosol microphysics //---------------------- @@ -922,14 +997,14 @@ void MAMMicrophysics::run_impl(const double dt) { // subroutine in eam/src/chemistry/modal_aero/aero_model.F90 // aqueous chemistry ... - const int loffset = - 8; // offset of first tracer in work arrays - // (taken from mam4xx setsox validation test) const Real mbar = haero::Constants::molec_weight_dry_air; constexpr int indexm = mam4::gas_chemistry::indexm; mam4::mo_setsox::setsox_single_level( - loffset, dt, pmid, pdel, temp, mbar, lwc, cldfrac, cldnum, - invariants_k[indexm], config.setsox, vmrcw, vmr); + // in + offset_aerosol, dt, pmid, pdel, temp, mbar, lwc, cldfrac, + cldnum, invariants_k[indexm], config.setsox, + // out + vmrcw, vmr); // calculate aerosol water content using water uptake treatment // * dry and wet diameters [m] @@ -940,19 +1015,24 @@ void MAMMicrophysics::run_impl(const double dt) { Real wetdens_kk[num_modes] = {}; Real qaerwat_kk[num_modes] = {}; - for (int imode = 0; imode < num_modes; imode++) { - dgncur_awet_kk[imode] = wet_diameter_icol(imode, k); - dgncur_a_kk[imode] = dry_diameter_icol(imode, k); - qaerwat_kk[imode] = qaerwat_icol(imode, k); - wetdens_kk[imode] = wetdens_icol(imode, k); + for(int imode = 0; imode < num_modes; imode++) { + dgncur_awet_kk[imode] = wet_diameter_icol(imode, kk); + dgncur_a_kk[imode] = dry_diameter_icol(imode, kk); + wetdens_kk[imode] = wetdens_icol(imode, kk); } // do aerosol microphysics (gas-aerosol exchange, nucleation, // coagulation) + // FIXME: Verify cldfrac is the right one by looking at EAM + // variable impl::modal_aero_amicphys_intr( - config.amicphys, step, dt, temp, pmid, pdel, zm, pblh, qv, - cldfrac, vmr, vmrcw, vmr_pregaschem, vmr_precldchem, - vmrcw_precldchem, vmr_tendbb, vmrcw_tendbb, dgncur_a_kk, - dgncur_awet_kk, wetdens_kk, qaerwat_kk); + // in + config.amicphys, dt, temp, pmid, pdel, zm, pblh, qv, cldfrac, + // out + vmr, vmrcw, + // in + vmr0, vmr_pregas, vmr_precld, dgncur_a_kk, dgncur_awet_kk, + wetdens_kk); + //----------------- // LINOZ chemistry //----------------- @@ -963,18 +1043,18 @@ void MAMMicrophysics::run_impl(const double dt) { o3clim_linoz_diag, zenith_angle_degrees; int o3_ndx = 0; // index of "O3" in solsym array (in EAM) - - mam4::lin_strat_chem::lin_strat_chem_solve_kk(o3_col_dens_i(k), temp, - zenith_angle, pmid, dt, rlats, - linoz_o3_clim(icol, k), linoz_t_clim(icol, k), linoz_o3col_clim(icol, k), - linoz_PmL_clim(icol, k), linoz_dPmL_dO3(icol, k), linoz_dPmL_dT(icol, k), - linoz_dPmL_dO3col(icol, k), linoz_cariolle_pscs(icol, k), - chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], - do3_linoz, do3_linoz_psc, ss_o3, - o3col_du_diag, o3clim_linoz_diag, zenith_angle_degrees); + mam4::lin_strat_chem::lin_strat_chem_solve_kk( + o3_col_dens_i(kk), temp, zenith_angle(icol), pmid, dt, rlats, + linoz_o3_clim(icol, kk), linoz_t_clim(icol, kk), + linoz_o3col_clim(icol, kk), linoz_PmL_clim(icol, kk), + linoz_dPmL_dO3(icol, kk), linoz_dPmL_dT(icol, kk), + linoz_dPmL_dO3col(icol, kk), linoz_cariolle_pscs(icol, kk), + chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], do3_linoz, + do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, + zenith_angle_degrees); // update source terms above the ozone decay threshold - /*if (k > nlev - config.linoz.o3_lbl - 1) { + /*if (kk > nlev - config.linoz.o3_lbl - 1) { Real do3_mass; // diagnostic, not needed mam4::lin_strat_chem::lin_strat_sfcsink_kk(dt, pdel, vmr[o3_ndx], config.linoz.o3_sfc, config.linoz.o3_tau, do3_mass); @@ -992,22 +1072,24 @@ void MAMMicrophysics::run_impl(const double dt) { // FIXME: C++ port in progress! // mam4::drydep::drydep_xactive(...); - mam_coupling::vmr2mmr(vmr, adv_mass_kg_per_moles, q); + mam_coupling::vmr2mmr(vmr, adv_mass_kg_per_moles, qq); mam_coupling::vmr2mmr(vmrcw, adv_mass_kg_per_moles, qqcw); - for (int i = offset_aerosol; i < pcnst; ++i) { - state_q[i] = q[i-offset_aerosol]; - qqcw_long[i] = qqcw[i-offset_aerosol]; + for(int i = offset_aerosol; i < pcnst; ++i) { + state_q[i] = qq[i - offset_aerosol]; + qqcw_pcnst[i] = qqcw[i - offset_aerosol]; } - mam4::utils::inject_stateq_to_prognostics(state_q, progs, k); - mam4::utils::inject_qqcw_to_prognostics(qqcw_long, progs, k); - }); - }); + mam4::utils::inject_stateq_to_prognostics(state_q, progs, kk); + mam4::utils::inject_qqcw_to_prognostics(qqcw_pcnst, progs, kk); + }); // parallel_for for vertical levels + }); // parallel_for for the column loop + Kokkos::fence(); // postprocess output Kokkos::parallel_for("postprocess", policy, postprocess_); Kokkos::fence(); -} + +} // MAMMicrophysics::run_impl void MAMMicrophysics::finalize_impl() {} diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index abf4c023a07..87fca4d5fff 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -42,6 +42,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { using const_view_1d = typename KT::template view_1d; using const_view_2d = typename KT::template view_2d; + using view_1d_host = typename KT::view_1d::HostMirror; + // unmanaged views (for buffer and workspace manager) using uview_1d = Unmanaged>; using uview_2d = Unmanaged>; @@ -107,6 +109,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // configuration data (for the moment, we plan to be able to move this to // the device, so we can't use C++ strings) +#define MAX_FILENAME_LEN 256 struct Config { // photolysis parameters struct { @@ -279,6 +282,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_3d extfrc_; mam_coupling::ForcingHelper forcings_[mam4::gas_chemistry::extcnt]; + view_1d_host acos_cosine_zenith_host_; + view_1d acos_cosine_zenith_; }; // MAMMicrophysics diff --git a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp index cf2dbea079e..d3c4e172b58 100644 --- a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp +++ b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp @@ -3,36 +3,43 @@ namespace scream::impl { using View2D = DeviceType::view_2d; KOKKOS_INLINE_FUNCTION -void compute_o3_column_density(const ThreadTeam &team, - const haero::Atmosphere &atm, - const mam4::Prognostics &progs, - const View2D &invariants, - ColumnView o3_col_dens) { +void compute_o3_column_density( + const ThreadTeam &team, const haero::Atmosphere &atm, + const mam4::Prognostics &progs, const View2D &invariants, + const Real adv_mass_kg_per_moles[mam4::gas_chemistry::gas_pcnst], + ColumnView o3_col_dens) { constexpr int gas_pcnst = mam4::gas_chemistry::gas_pcnst; // number of gas phase species constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" + constexpr int offset_aerosol = mam4::utils::gasses_start_ind(); Real o3_col_deltas[mam4::nlev + 1] = {}; // o3 column density above model [1/cm^2] // NOTE: if we need o2 column densities, set_ub_col and setcol must be changed Kokkos::parallel_for( - Kokkos::TeamThreadRange(team, atm.num_levels()), [&](const int k) { + Kokkos::TeamThreadRange(team, mam4::nlev), [&](const int k) { Real pdel = atm.hydrostatic_dp(k); + // extract aerosol state variables into "working arrays" (mass + // mixing ratios) (in EAM, this is done in the gas_phase_chemdr + // subroutine defined within + // mozart/mo_gas_phase_chemdr.F90) + Real q[gas_pcnst] = {}; + Real state_q[pcnst] = {}; + mam4::utils::extract_stateq_from_prognostics(progs, atm, state_q, k); + + for(int i = offset_aerosol; i < pcnst; ++i) { + q[i - offset_aerosol] = state_q[i]; + } - // ... map incoming mass mixing ratios to working array - Real q[gas_pcnst], qqcw[gas_pcnst]; - mam_coupling::transfer_prognostics_to_work_arrays(progs, k, q, qqcw); - - // ... Xform from mmr to vmr - Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - mam_coupling::convert_work_arrays_to_vmr(q, qqcw, vmr, vmrcw); - + // convert mass mixing ratios to volume mixing ratios (VMR), + // equivalent to tracer mixing ratios (TMR)) + Real vmr[gas_pcnst]; + mam_coupling::mmr2vmr(q, adv_mass_kg_per_moles, vmr); // ... compute invariants for this level Real invariants_k[nfs]; for(int i = 0; i < nfs; ++i) { invariants_k[i] = invariants(k, i); } - // compute the change in o3 density for this column above its neighbor mam4::mo_photo::set_ub_col(o3_col_deltas[k + 1], vmr, invariants_k, pdel); diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp index ff581db86fb..d977340b995 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp @@ -4,130 +4,60 @@ namespace scream::impl { using mam4::utils::min_max_bound; +// The following variables are chemistry mechanism dependent! See +// mam4xx/src/mam4xx/gas_chem_mechanism.hpp) + +// number of gases+aerosols species +using mam4::gas_chemistry::gas_pcnst; +// number of species with external forcing +using mam4::gas_chemistry::extcnt; +// index of total atm density in invariant array +using mam4::gas_chemistry::indexm; +// number of chemical reactions +using mam4::gas_chemistry::rxntot; +// number of photolysis reactions +using mam4::mo_photo::phtcnt; +// number of invariants +using mam4::gas_chemistry::nfs; + +// FIXME: Hardwired indices, should be obtained from a config file +constexpr int ndx_h2so4 = 2; +constexpr int synoz_ndx = -1; + +// Following (indices (ndxes?) are taken from mam4 validation data and +// translated from +// 1-based indices to 0-based indices) +constexpr int usr_HO2_HO2_ndx = 1, usr_DMS_OH_ndx = 5, usr_SO2_OH_ndx = 3, + inv_h2o_ndx = 3; + using HostView1D = haero::DeviceType::view_1d::HostMirror; using HostViewInt1D = haero::DeviceType::view_1d::HostMirror; //------------------------------------------------------------------------- // Reading the photolysis table //------------------------------------------------------------------------- -// This logic is currently implemented using serial NetCDF calls for -// clarity of purpose. We should probably read the data for the photolysis -// table using SCREAM's SCORPIO interface instead, but I wanted to make -// clear what we're trying to do in terms of "elementary" operations first. - -// ON HOST (MPI root rank only), reads the dimension of a NetCDF variable from -// the file with the given ID -int nc_dimension(const char *file, int nc_id, const char *dim_name) { - int dim_id; - int result = nc_inq_dimid(nc_id, dim_name, &dim_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " - << dim_name - << " dimension ID from NetCDF file '" - << file << "'\n"); - size_t dim; - result = nc_inq_dimlen(nc_id, dim_id, &dim); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch " - << dim_name - << " dimension from NetCDF file '" << file - << "'\n"); - return static_cast(dim); -} - -// ON HOST (MPI root rank only), reads data from the given NetCDF variable from -// the file with the given ID into the given Kokkos host View -template -void read_nc_var(const char *file, int nc_id, const char *var_name, - V host_view) { - int var_id; - int result = nc_inq_varid(nc_id, var_name, &var_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch ID for variable '" - << var_name << "' from NetCDF file '" - << file << "'\n"); - result = nc_get_var(nc_id, var_id, host_view.data()); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't read data for variable '" - << var_name << "' from NetCDF file '" - << file << "'\n"); -} - -// ON HOST (MPI root rank only), reads data from the NetCDF variable with the -// given ID, from the file with the given ID, into the given Kokkos host View -template -void read_nc_var(const char *file, int nc_id, int var_id, V host_view) { - int result = nc_get_var(nc_id, var_id, host_view.data()); - EKAT_REQUIRE_MSG(result == 0, - "Error! Couldn't read data for variable with ID " - << var_id << " from NetCDF file '" << file << "'\n"); -} // ON HOST (MPI root only), sets the lng_indexer and pht_alias_mult_1 host views // according to parameters in our (hardwired) chemical mechanism -void set_lng_indexer_and_pht_alias_mult_1(const char *file, int nc_id, - HostViewInt1D lng_indexer, - HostView1D pht_alias_mult_1) { - // NOTE: it seems that the chemical mechanism we're using - // NOTE: 1. sets pht_alias_lst to a blank string [1] - // NOTE: 2. sets pht_alias_mult_1 to 1.0 [1] - // NOTE: 3. sets rxt_tag_lst to ['jh2o2', 'usr_HO2_HO2', 'usr_SO2_OH', - // 'usr_DMS_OH'] [2] NOTE: References: NOTE: [1] - // (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L117) - // NOTE: [2] - // (https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/pp_linoz_mam4_resus_mom_soag/mo_sim_dat.F90#L99) - - // populate lng_indexer (see - // https://github.com/eagles-project/e3sm_mam4_refactor/blob/refactor-maint-2.0/components/eam/src/chemistry/mozart/mo_jlong.F90#L180) - static const char *var_names[4] = {"jh2o2", "usr_HO2_HO2", "usr_SO2_OH", - "usr_DMS_OH"}; - for(int m = 0; m < mam4::mo_photo::phtcnt; ++m) { - int var_id; - int result = nc_inq_varid(nc_id, var_names[m], &var_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't fetch ID for variable '" - << var_names[m] << "' from NetCDF file '" - << file << "'\n"); - lng_indexer(m) = var_id; - } - - // set pht_alias_mult_1 to 1 - Kokkos::deep_copy(pht_alias_mult_1, 1.0); -} - -// ON HOST (MPI root only), populates the etfphot view using rebinned -// solar data from our solar_data_file -void populate_etfphot(HostView1D we, HostView1D etfphot) { - // FIXME: It looks like EAM is relying on a piece of infrastructure that - // FIXME: we just don't have in EAMxx - // (eam/src/chemistry/utils/solar_data.F90). - // FIXME: I have no idea whether EAMxx has a plan for supporting this - // FIXME: solar irradiance / photon flux data, and I'm not going to recreate - // FIXME: that capability here. So this is an unplugged hole. - // FIXME: - // FIXME: If we are going to do this the way EAM does it, the relevant logic - // FIXME: is the call to rebin() in eam/src/chemistry/mozart/mo_jlong.F90, - // FIXME: around line 104. - - // FIXME: zero the photon flux for now - Kokkos::deep_copy(etfphot, 0); -} -std::vector populate_etfphot_from_e3sm_case() -{ +std::vector populate_etfphot_from_e3sm_case() { // We obtained these values from an e3sm simulations. // We should only use this function on Host. std::vector etfphot_data = { - 7.5691227E+11, 8.6525905E+11, 1.0355749E+12, 1.1846288E+12, 2.1524405E+12, - 3.2362584E+12, 3.7289849E+12, 4.4204330E+12, 4.6835350E+12, 6.1217728E+12, - 4.5575051E+12, 5.3491446E+12, 4.7016063E+12, 5.4281722E+12, 4.5023968E+12, - 6.8931981E+12, 6.2012647E+12, 6.1430771E+12, 5.7820385E+12, 7.6770646E+12, - 1.3966509E+13, 1.2105348E+13, 2.8588980E+13, 3.2160821E+13, 2.4978066E+13, - 2.7825401E+13, 2.3276451E+13, 3.6343684E+13, 6.1787886E+13, 7.8009914E+13, - 7.6440824E+13, 7.6291458E+13, 9.4645085E+13, 1.0124628E+14, 1.0354111E+14, - 1.0999650E+14, 1.0889946E+14, 1.1381912E+14, 1.3490042E+14, 1.5941519E+14, - 1.4983265E+14, 1.5184267E+14, 1.5991420E+14, 1.6976697E+14, 1.8771840E+14, - 1.6434367E+14, 1.8371960E+14, 2.1966369E+14, 1.9617879E+14, 2.2399700E+14, - 1.8429912E+14, 2.0129736E+14, 2.0541588E+14, 2.4334962E+14, 3.5077122E+14, - 3.4517894E+14, 3.5749668E+14, 3.6624304E+14, 3.4975113E+14, 3.5566025E+14, - 4.2825273E+14, 4.8406375E+14, 4.9511159E+14, 5.2695368E+14, 5.2401611E+14, - 5.0877746E+14, 4.8780853E+14 - }; + 7.5691227E+11, 8.6525905E+11, 1.0355749E+12, 1.1846288E+12, 2.1524405E+12, + 3.2362584E+12, 3.7289849E+12, 4.4204330E+12, 4.6835350E+12, 6.1217728E+12, + 4.5575051E+12, 5.3491446E+12, 4.7016063E+12, 5.4281722E+12, 4.5023968E+12, + 6.8931981E+12, 6.2012647E+12, 6.1430771E+12, 5.7820385E+12, 7.6770646E+12, + 1.3966509E+13, 1.2105348E+13, 2.8588980E+13, 3.2160821E+13, 2.4978066E+13, + 2.7825401E+13, 2.3276451E+13, 3.6343684E+13, 6.1787886E+13, 7.8009914E+13, + 7.6440824E+13, 7.6291458E+13, 9.4645085E+13, 1.0124628E+14, 1.0354111E+14, + 1.0999650E+14, 1.0889946E+14, 1.1381912E+14, 1.3490042E+14, 1.5941519E+14, + 1.4983265E+14, 1.5184267E+14, 1.5991420E+14, 1.6976697E+14, 1.8771840E+14, + 1.6434367E+14, 1.8371960E+14, 2.1966369E+14, 1.9617879E+14, 2.2399700E+14, + 1.8429912E+14, 2.0129736E+14, 2.0541588E+14, 2.4334962E+14, 3.5077122E+14, + 3.4517894E+14, 3.5749668E+14, 3.6624304E+14, 3.4975113E+14, 3.5566025E+14, + 4.2825273E+14, 4.8406375E+14, 4.9511159E+14, 5.2695368E+14, 5.2401611E+14, + 5.0877746E+14, 4.8780853E+14}; return etfphot_data; } @@ -168,7 +98,7 @@ mam4::mo_photo::PhotoTableData read_photo_table( auto colo3_h = Kokkos::create_mirror_view(table.colo3); auto o3rat_h = Kokkos::create_mirror_view(table.o3rat); // auto etfphot_h = Kokkos::create_mirror_view(table.etfphot); - auto prs_h = Kokkos::create_mirror_view(table.prs); + auto prs_h = Kokkos::create_mirror_view(table.prs); // read file data into our host views scorpio::read_var(rsf_file, "pm", press_h.data()); @@ -199,7 +129,7 @@ mam4::mo_photo::PhotoTableData read_photo_table( // populate_etfphot(we_h, etfphot_h); // FIXME: etfphot_data is hard-coded. auto etfphot_data = populate_etfphot_from_e3sm_case(); - auto etfphot_h = HostView1D((Real *)etfphot_data.data(),nw); + auto etfphot_h = HostView1D((Real *)etfphot_data.data(), nw); scorpio::release_file(rsf_file); scorpio::release_file(xs_long_file); @@ -243,259 +173,56 @@ mam4::mo_photo::PhotoTableData read_photo_table( return table; } -#if 0 -// ON HOST, reads the photolysis table (used for gas phase chemistry) from the -// files with the given names -mam4::mo_photo::PhotoTableData read_photo_table(const ekat::Comm& comm, - const char *rsf_file, - const char* xs_long_file) { - // NOTE: at the time of development, SCREAM's SCORPIO interface seems intended - // NOTE: for domain-decomposed grid data. The files we're reading here are not - // NOTE: spatial data, and should be the same everywhere, so we read them - // NOTE: using serial NetCDF calls on MPI rank 0 and broadcast to other ranks. - const int mpi_root = 0; - int rsf_id, xs_long_id; // NetCDF file IDs (used only on MPI root) - int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions - if (comm.rank() == mpi_root) { // read dimension data from files and broadcast - // open files - int result = nc_open(rsf_file, NC_NOWRITE, &rsf_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't open rsf_file '" << rsf_file << "'\n"); - result = nc_open(xs_long_file, NC_NOWRITE, &xs_long_id); - EKAT_REQUIRE_MSG(result == 0, "Error! Couldn't open xs_long_file '" << xs_long_file << "'\n"); - - // read and broadcast dimension data - nump = nc_dimension(rsf_file, rsf_id, "numz"); - numsza = nc_dimension(rsf_file, rsf_id, "numsza"); - numalb = nc_dimension(rsf_file, rsf_id, "numalb"); - numcolo3 = nc_dimension(rsf_file, rsf_id, "numcolo3fact"); - nt = nc_dimension(xs_long_file, xs_long_id, "numtemp"); - nw = nc_dimension(xs_long_file, xs_long_id, "numwl"); - np_xs = nc_dimension(xs_long_file, xs_long_id, "numprs"); - std::cout < 0) { - for (int mm = 0; mm < m; ++mm) { - if (lng_indexer_h(mm) == lng_indexer_h(m)) { - break; - } - ++numj; - } - } - } - - // allocate the photolysis table - auto table = mam4::mo_photo::create_photo_table_data(nw, nt, np_xs, numj, - nump, numsza, numcolo3, - numalb); - - // allocate host views for table data - auto rsf_tab_h = Kokkos::create_mirror_view(table.rsf_tab); - auto xsqy_h = Kokkos::create_mirror_view(table.xsqy); - auto sza_h = Kokkos::create_mirror_view(table.sza); - auto alb_h = Kokkos::create_mirror_view(table.alb); - auto press_h = Kokkos::create_mirror_view(table.press); - auto colo3_h = Kokkos::create_mirror_view(table.colo3); - auto o3rat_h = Kokkos::create_mirror_view(table.o3rat); - auto etfphot_h = Kokkos::create_mirror_view(table.etfphot); - auto prs_h = Kokkos::create_mirror_view(table.prs); - - if (comm.rank() == mpi_root) { // read data from files and broadcast - // read file data into our host views - read_nc_var(rsf_file, rsf_id, "pm", press_h); - read_nc_var(rsf_file, rsf_id, "sza", sza_h); - read_nc_var(rsf_file, rsf_id, "alb", alb_h); - read_nc_var(rsf_file, rsf_id, "colo3fact", o3rat_h); - read_nc_var(rsf_file, rsf_id, "colo3", colo3_h); - read_nc_var(rsf_file, rsf_id, "RSF", rsf_tab_h); - - read_nc_var(xs_long_file, xs_long_id, "pressure", prs_h); - - // read xsqy data (using lng_indexer_h for the first index) - int ndx = 0; - for (int m = 0; m < mam4::mo_photo::phtcnt; ++m) { - if (lng_indexer_h(m) > 0) { - auto xsqy_ndx_h = ekat::subview(xsqy_h, ndx); - read_nc_var(xs_long_file, xs_long_id, lng_indexer_h(m), xsqy_ndx_h); - ++ndx; - } - } - - // populate etfphot by rebinning solar data - HostView1D wc_h("wc", nw), wlintv_h("wlintv", nw), we_h("we", nw+1); - read_nc_var(rsf_file, rsf_id, "wc", wc_h); - read_nc_var(rsf_file, rsf_id, "wlintv", wlintv_h); - for (int i = 0; i < nw; ++i) { - we_h(i) = wc_h(i) - 0.5 * wlintv_h(i); - } - we_h(nw) = wc_h(nw-1) - 0.5 * wlintv_h(nw-1); - populate_etfphot(we_h, etfphot_h); - - // close the files - nc_close(rsf_id); - nc_close(xs_long_id); - } - - // broadcast host views from MPI root to others - comm.broadcast(rsf_tab_h.data(), nw*numalb*numcolo3*numsza*nump, mpi_root); - comm.broadcast(xsqy_h.data(), numj*nw*nt*np_xs, mpi_root); - comm.broadcast(sza_h.data(), numsza, mpi_root); - comm.broadcast(alb_h.data(), numalb, mpi_root); - comm.broadcast(press_h.data(), nump, mpi_root); - comm.broadcast(o3rat_h.data(), numcolo3, mpi_root); - comm.broadcast(colo3_h.data(), nump, mpi_root); - comm.broadcast(etfphot_h.data(), nw, mpi_root); - comm.broadcast(prs_h.data(), np_xs, mpi_root); - - // copy host photolysis table into place on device - Kokkos::deep_copy(table.rsf_tab, rsf_tab_h); - Kokkos::deep_copy(table.xsqy, xsqy_h); - Kokkos::deep_copy(table.sza, sza_h); - Kokkos::deep_copy(table.alb, alb_h); - Kokkos::deep_copy(table.press, press_h); - Kokkos::deep_copy(table.colo3, colo3_h); - Kokkos::deep_copy(table.o3rat, o3rat_h); - Kokkos::deep_copy(table.etfphot, etfphot_h); - Kokkos::deep_copy(table.prs, prs_h); - Kokkos::deep_copy(table.pht_alias_mult_1, pht_alias_mult_1_h); - Kokkos::deep_copy(table.lng_indexer, lng_indexer_h); - - // compute gradients (on device) - Kokkos::parallel_for("del_p", nump-1, KOKKOS_LAMBDA(int i) { - table.del_p(i) = 1.0/::abs(table.press(i)- table.press(i+1)); - }); - Kokkos::parallel_for("del_sza", numsza-1, KOKKOS_LAMBDA(int i) { - table.del_sza(i) = 1.0/(table.sza(i+1) - table.sza(i)); - }); - Kokkos::parallel_for("del_alb", numalb-1, KOKKOS_LAMBDA(int i) { - table.del_alb(i) = 1.0/(table.alb(i+1) - table.alb(i)); - }); - Kokkos::parallel_for("del_o3rat", numcolo3-1, KOKKOS_LAMBDA(int i) { - table.del_o3rat(i) = 1.0/(table.o3rat(i+1) - table.o3rat(i)); - }); - Kokkos::parallel_for("dprs", np_xs-1, KOKKOS_LAMBDA(int i) { - table.dprs(i) = 1.0/(table.prs(i) - table.prs(i+1)); - }); - - return table; -} -#endif +// ================================================================ +// Gas Phase Chemistry +// ================================================================ // performs gas phase chemistry calculations on a single level of a single // atmospheric column KOKKOS_INLINE_FUNCTION void gas_phase_chemistry( - Real zm, Real zi, Real phis, Real temp, Real pmid, Real pdel, Real dt, - const Real photo_rates[mam4::mo_photo::phtcnt], // in - const Real extfrc[mam4::gas_chemistry::extcnt], // in - Real invariants[mam4::gas_chemistry::nfs], // in - const int clsmap_4[mam4::gas_chemistry::gas_pcnst], // in - const int permute_4[mam4::gas_chemistry::gas_pcnst],// in - Real q[mam4::gas_chemistry::gas_pcnst]) { // VMRs, inout - // constexpr Real rga = 1.0/haero::Constants::gravity; - // constexpr Real m2km = 0.01; // converts m -> km - - // The following things are chemical mechanism dependent! See - // mam4xx/src/mam4xx/gas_chem_mechanism.hpp) - constexpr int gas_pcnst = - mam4::gas_chemistry::gas_pcnst; // number of gas phase species - constexpr int rxntot = - mam4::gas_chemistry::rxntot; // number of chemical reactions - constexpr int extcnt = - mam4::gas_chemistry::extcnt; // number of species with external forcing - constexpr int indexm = - mam4::gas_chemistry::indexm; // index of total atm density in invariant - // array - - constexpr int phtcnt = - mam4::mo_photo::phtcnt; // number of photolysis reactions - - constexpr int itermax = mam4::gas_chemistry::itermax; - constexpr int clscnt4 = mam4::gas_chemistry::clscnt4; - // These indices for species are fixed by the chemical mechanism - // std::string solsym[] = {"O3", "H2O2", "H2SO4", "SO2", "DMS", "SOAG", - // "so4_a1", "pom_a1", "soa_a1", "bc_a1", "dst_a1", - // "ncl_a1", "mom_a1", "num_a1", "so4_a2", "soa_a2", - // "ncl_a2", "mom_a2", "num_a2", "dst_a3", "ncl_a3", - // "so4_a3", "bc_a3", "pom_a3", "soa_a3", "mom_a3", - // "num_a3", "pom_a4", "bc_a4", "mom_a4", "num_a4"}; - constexpr int ndx_h2so4 = 2; - // Q: note that "num_a3" is not part of this list in the e3sm refactored code. - // std::string extfrc_list[] = {"SO2", "so4_a1", "so4_a2", "pom_a4", "bc_a4", - // "num_a1", "num_a2", "num_a3", "num_a4", - // "SOAG"}; - constexpr int synoz_ndx = -1; - - // fetch the zenith angle (not its cosine!) in degrees for this column. - // FIXME: For now, we fix the zenith angle. At length, we need to compute it - // FIXME: from EAMxx's current set of orbital parameters, which requires some - // FIXME: conversation with the EAMxx team. - - // xform geopotential height from m to km and pressure from Pa to mb - // Real zsurf = rga * phis; - // Real zmid = m2km * (zm + zsurf); - + // in + const Real temp, const Real dt, + const Real photo_rates[mam4::mo_photo::phtcnt], const Real extfrc[extcnt], + const Real invariants[nfs], const int (&clsmap_4)[gas_pcnst], + const int (&permute_4)[gas_pcnst], + // out + Real (&qq)[gas_pcnst], Real (&vmr0)[gas_pcnst]) { + //===================================================================== // ... set rates for "tabular" and user specified reactions + //===================================================================== Real reaction_rates[rxntot]; - mam4::gas_chemistry::setrxt(reaction_rates, temp); + mam4::gas_chemistry::setrxt(reaction_rates, // out + temp); // in // set reaction rates based on chemical invariants - // (indices (ndxes?) are taken from mam4 validation data and translated from - // 1-based indices to 0-based indices) - int usr_HO2_HO2_ndx = 1, usr_DMS_OH_ndx = 5, usr_SO2_OH_ndx = 3, - inv_h2o_ndx = 3; - mam4::gas_chemistry::usrrxt(reaction_rates, temp, invariants, - invariants[indexm], usr_HO2_HO2_ndx, - usr_DMS_OH_ndx, usr_SO2_OH_ndx, inv_h2o_ndx); - mam4::gas_chemistry::adjrxt(reaction_rates, invariants, invariants[indexm]); + mam4::gas_chemistry::usrrxt(reaction_rates, // out + temp, invariants, invariants[indexm], // in + usr_HO2_HO2_ndx, usr_DMS_OH_ndx, // in + usr_SO2_OH_ndx, // in + inv_h2o_ndx); // in + + mam4::gas_chemistry::adjrxt(reaction_rates, // out + invariants, invariants[indexm]); // in //=================================== // Photolysis rates at time = t(n+1) //=================================== // compute the rate of change from forcing - Real extfrc_rates[extcnt]; // [1/cm^3/s] - for (int mm = 0; mm < extcnt; ++mm) { - if (mm != synoz_ndx) { + Real extfrc_rates[extcnt]; // [1/cm^3/s] + for(int mm = 0; mm < extcnt; ++mm) { + if(mm != synoz_ndx) { extfrc_rates[mm] = extfrc[mm] / invariants[indexm]; } } // ... Form the washout rates - Real het_rates[gas_pcnst]={0}; - // FIXME: not ported yet - // sethet(het_rates, pmid, zmid, phis, temp, cmfdqr, prain, nevapr, delt, - // invariants[indexm], q); - + // FIXME: het_rates will be provided by sethet routine to be called before + // the vertical level parallel_for in the interface + Real het_rates[gas_pcnst] = {0}; // save h2so4 before gas phase chem (for later new particle nucleation) - Real del_h2so4_gasprod = q[ndx_h2so4]; + Real del_h2so4_gasprod = qq[ndx_h2so4]; //=========================== // Class solution algorithms @@ -508,23 +235,31 @@ void gas_phase_chemistry( } // ... solve for "Implicit" species + using mam4::gas_chemistry::itermax; bool factor[itermax]; for(int i = 0; i < itermax; ++i) { factor[i] = true; } // initialize error tolerances + using mam4::gas_chemistry::clscnt4; Real epsilon[clscnt4]; mam4::gas_chemistry::imp_slv_inti(epsilon); + // Mixing ratios before chemistry changes + for(int i = 0; i < gas_pcnst; ++i) { + vmr0[i] = qq[i]; + } + // solve chemical system implicitly Real prod_out[clscnt4], loss_out[clscnt4]; - mam4::gas_chemistry::imp_sol(q, reaction_rates, het_rates, extfrc_rates, dt, permute_4, - clsmap_4, factor, epsilon, prod_out, loss_out); + mam4::gas_chemistry::imp_sol(qq, reaction_rates, het_rates, extfrc_rates, dt, + permute_4, clsmap_4, factor, epsilon, prod_out, + loss_out); // save h2so4 change by gas phase chem (for later new particle nucleation) if(ndx_h2so4 > 0) { - del_h2so4_gasprod = q[ndx_h2so4] - del_h2so4_gasprod; + del_h2so4_gasprod = qq[ndx_h2so4] - del_h2so4_gasprod; } } diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index 09badd5695d..be989971541 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -15,13 +15,13 @@ namespace scream::mam_coupling { using namespace ShortFieldTagsNames; using view_1d_host = typename KT::view_1d::HostMirror; +using view_2d_host = typename KT::view_2d::HostMirror; using ExeSpace = typename KT::ExeSpace; using ESU = ekat::ExeSpaceUtils; using C = scream::physics::Constants; using LIV = ekat::LinInterp; - // Linoz NetCDF files use levs instead of formula_terms. // This function allocates a view, so we need to do it during initialization. // Thus, we assume that source pressure is independent of time, @@ -31,9 +31,8 @@ inline void compute_p_src_zonal_files(const view_1d &levs, const view_2d &p_src) { EKAT_REQUIRE_MSG(p_src.data() != 0, "Error: p_src has not been allocated. \n"); - EKAT_REQUIRE_MSG(levs.data() != 0, - "Error: levs has not been allocated. \n"); - const int ncol = p_src.extent(0); + EKAT_REQUIRE_MSG(levs.data() != 0, "Error: levs has not been allocated. \n"); + const int ncol = p_src.extent(0); const int nlevs_data = levs.extent(0); EKAT_REQUIRE_MSG( int(p_src.extent(1)) == nlevs_data, @@ -68,7 +67,7 @@ struct ForcingHelper { int nsectors; // offset in output vector from reader int offset; - }; +}; enum TracerFileType { // file with PS ncol, lev, and time @@ -79,17 +78,13 @@ enum TracerFileType { VERT_EMISSION, }; -enum TracerDataIndex { - BEG=0, - END=1, - OUT=2 -}; +enum TracerDataIndex { BEG = 0, END = 1, OUT = 2 }; /* Maximum number of tracers (or fields) that the tracer reader can handle. Note: We are not allocating memory for MAX_NVARS_TRACER tracers. Therefore, if a file contains more than this number, it is acceptable to increase this limit. Currently, Linoz files have 8 fields. */ -constexpr int MAX_NVARS_TRACER = 10; +constexpr int MAX_NVARS_TRACER = 10; constexpr int MAX_NUM_VERT_EMISSION_FIELDS = 25; // Linoz structures to help manage all of the variables: @@ -142,7 +137,8 @@ struct TracerData { view_1d ps[3]; const_view_1d hyam; const_view_1d hybm; - view_int_1d work_vert_inter[MAX_NVARS_TRACER];; + view_int_1d work_vert_inter[MAX_NVARS_TRACER]; + ; // External forcing file (vertical emission) // Uses altitude instead of pressure to interpolate data @@ -155,9 +151,8 @@ struct TracerData { // only for zonal files view_1d zonal_levs_; - void allocate_temporal_views() - { - //BEG and OUT data views. + void allocate_temporal_views() { + // BEG and OUT data views. EKAT_REQUIRE_MSG(ncol_ != int(-1), "Error! ncols has not been set. \n"); EKAT_REQUIRE_MSG(nlev_ != int(-1), "Error! nlevs has not been set. \n"); EKAT_REQUIRE_MSG(nvars_ != int(-1), "Error! nvars has not been set. \n"); @@ -168,16 +163,15 @@ struct TracerData { } // for vertical interpolation using rebin routine - if(file_type == FORMULA_PS || - file_type == ZONAL) { - // we only need work array for FORMULA_PS or ZONAL - for(int ivar = 0; ivar < nvars_; ++ivar) { - work_vert_inter[ivar] = - view_int_1d("allocate_work_vertical_interpolation", ncol_); - } - // we use ncremap and python scripts to convert zonal files to ne4pn4 - // grids. - p_src_ = view_2d("pressure_src_invariant", ncol_, nlev_); + if(file_type == FORMULA_PS || file_type == ZONAL) { + // we only need work array for FORMULA_PS or ZONAL + for(int ivar = 0; ivar < nvars_; ++ivar) { + work_vert_inter[ivar] = + view_int_1d("allocate_work_vertical_interpolation", ncol_); + } + // we use ncremap and python scripts to convert zonal files to ne4pn4 + // grids. + p_src_ = view_2d("pressure_src_invariant", ncol_, nlev_); } if(file_type == TracerFileType::FORMULA_PS) { @@ -190,58 +184,9 @@ struct TracerData { // grids. compute_p_src_zonal_files(zonal_levs_, p_src_); } - } }; -// Direct port of components/eam/src/chemistry/utils/tracer_data.F90/vert_interp -// FIXME: I need to convert for loops to Kokkos loops. -KOKKOS_INLINE_FUNCTION -void vert_interp(int ncol, int levsiz, int pver, const view_2d &pin, - const const_view_2d &pmid, const view_2d &datain, - const view_2d &dataout, - // work array - const view_int_1d &kupper) { - const int one = 1; - // Initialize index array - for(int i = 0; i < ncol; ++i) { - kupper(i) = one; - } // ncol - - for(int k = 0; k < pver; ++k) { - // Top level we need to start looking is the top level for the previous k - // for all column points - int kkstart = levsiz; - for(int i = 0; i < ncol; ++i) { - kkstart = haero::min(kkstart, kupper(i)); - } - - // Store level indices for interpolation - for(int kk = kkstart - 1; kk < levsiz - 1; ++kk) { - for(int i = 0; i < ncol; ++i) { - if(pin(i, kk) < pmid(i, k) && pmid(i, k) <= pin(i, kk + 1)) { - kupper(i) = kk; - } // end if - } // end for - } // end kk - // Interpolate or extrapolate... - for(int i = 0; i < ncol; ++i) { - if(pmid(i, k) < pin(i, 0)) { - dataout(i, k) = datain(i, 0) * pmid(i, k) / pin(i, 0); - } else if(pmid(i, k) > pin(i, levsiz - 1)) { - dataout(i, k) = datain(i, levsiz - 1); - } else { - Real dpu = pmid(i, k) - pin(i, kupper(i)); - Real dpl = pin(i, kupper(i) + 1) - pmid(i, k); - dataout(i, k) = - (datain(i, kupper(i)) * dpl + datain(i, kupper(i) + 1) * dpu) / - (dpl + dpu); - } // end if - } // end col - } // end k - -} // vert_interp - KOKKOS_INLINE_FUNCTION Real linear_interp(const Real &x0, const Real &x1, const Real &t) { return (1 - t) * x0 + t * x1; @@ -289,32 +234,33 @@ inline void create_linoz_chlorine_reader( // Gets the times from the NC file // Given a date in the format YYYYMMDD, returns its index in the time dimension. -inline void get_time_from_ncfile( - const std::string &file_name, - const int cyclical_ymd, // in format YYYYMMDD - int & cyclical_ymd_index, - std::vector &dates) { +inline void get_time_from_ncfile(const std::string &file_name, + const int cyclical_ymd, // in format YYYYMMDD + int &cyclical_ymd_index, + std::vector &dates) { // in file_name: name of the NC file // in cyclical_ymd: date in the format YYYYMMDD // out cyclical_ymd_index: time index for cyclical_ymd // out dates: date in YYYYMMDD format scorpio::register_file(file_name, scorpio::Read); const int nlevs_time = scorpio::get_time_len(file_name); - cyclical_ymd_index=-1; + cyclical_ymd_index = -1; for(int itime = 0; itime < nlevs_time; ++itime) { int date; scorpio::read_var(file_name, "date", &date, itime); // std::cout << itime << " date: " << date << "\n"; - if(date >= cyclical_ymd && cyclical_ymd_index ==-1) { - cyclical_ymd_index=itime; + if(date >= cyclical_ymd && cyclical_ymd_index == -1) { + cyclical_ymd_index = itime; } dates.push_back(date); } // end itime - EKAT_REQUIRE_MSG(cyclical_ymd_index>=0, - "Error! Current model time ("+std::to_string(cyclical_ymd)+") is not within "+ - "Tracer time period: ["+std::to_string(dates[0])+", "+ - "("+std::to_string(dates[nlevs_time-1])+").\n"); + EKAT_REQUIRE_MSG(cyclical_ymd_index >= 0, + "Error! Current model time (" + + std::to_string(cyclical_ymd) + ") is not within " + + "Tracer time period: [" + std::to_string(dates[0]) + + ", " + "(" + std::to_string(dates[nlevs_time - 1]) + + ").\n"); scorpio::release_file(file_name); } @@ -336,12 +282,12 @@ inline Real chlorine_loading_advance(const util::TimeStamp &ts, return values[index] + delt * (values[index + 1] - values[index]); } -// It reads variables that are not time-dependent and independent of columns (no MPI involved here). -// We also obtain the offset_time_index using a date (cyclical_ymd) as input. -// We initialize a few members of tracer_data. -inline void setup_tracer_data(TracerData &tracer_data, // out - const std::string& trace_data_file, // in - const int cyclical_ymd) //in +// It reads variables that are not time-dependent and independent of columns (no +// MPI involved here). We also obtain the offset_time_index using a date +// (cyclical_ymd) as input. We initialize a few members of tracer_data. +inline void setup_tracer_data(TracerData &tracer_data, // out + const std::string &trace_data_file, // in + const int cyclical_ymd) // in { scorpio::register_file(trace_data_file, scorpio::Read); // by default, I am assuming a zonal file. @@ -374,14 +320,13 @@ inline void setup_tracer_data(TracerData &tracer_data, // out scorpio::read_var(trace_data_file, "hybm", hybm_h.data()); view_1d hyam("hyam", nlevs_data); view_1d hybm("hybm", nlevs_data); - Kokkos::deep_copy(hyam,hyam_h); - Kokkos::deep_copy(hybm,hybm_h); + Kokkos::deep_copy(hyam, hyam_h); + Kokkos::deep_copy(hybm, hybm_h); tracer_data.hyam = hyam; tracer_data.hybm = hybm; } - if (tracer_file_type == ZONAL) - { + if(tracer_file_type == ZONAL) { view_1d_host levs_h("levs_h", nlevs_data); view_1d levs("levs", nlevs_data); scorpio::read_var(trace_data_file, "lev", levs_h.data()); @@ -389,47 +334,47 @@ inline void setup_tracer_data(TracerData &tracer_data, // out tracer_data.zonal_levs_ = levs; } - if (tracer_file_type == VERT_EMISSION) - { - const int nilevs_data = scorpio::get_dimlen(trace_data_file, "altitude_int"); + if(tracer_file_type == VERT_EMISSION) { + const int nilevs_data = + scorpio::get_dimlen(trace_data_file, "altitude_int"); view_1d_host altitude_int_host("altitude_int_host", nilevs_data); - view_1d altitude_int = view_1d("altitude_int",nilevs_data); - scorpio::read_var(trace_data_file, "altitude_int", altitude_int_host.data()); + view_1d altitude_int = view_1d("altitude_int", nilevs_data); + scorpio::read_var(trace_data_file, "altitude_int", + altitude_int_host.data()); Kokkos::deep_copy(altitude_int, altitude_int_host); - tracer_data.altitude_int_=altitude_int; + tracer_data.altitude_int_ = altitude_int; } - // time index + // time index { - const int nlevs_time = scorpio::get_dimlen(trace_data_file,"time"); - int cyclical_ymd_index=-1; - for(int itime = 0; itime < nlevs_time; ++itime) { - int date; - scorpio::read_var(trace_data_file, "date", &date, itime); - if(date >= cyclical_ymd) { - cyclical_ymd_index=itime; - break; - } - } // end itime + const int nlevs_time = scorpio::get_dimlen(trace_data_file, "time"); + int cyclical_ymd_index = -1; + for(int itime = 0; itime < nlevs_time; ++itime) { + int date; + scorpio::read_var(trace_data_file, "date", &date, itime); + if(date >= cyclical_ymd) { + cyclical_ymd_index = itime; + break; + } + } // end itime - EKAT_REQUIRE_MSG(cyclical_ymd_index>=0, - "Error! Current model time ("+std::to_string(cyclical_ymd)+") is not within "+ - "Tracer time period.\n"); + EKAT_REQUIRE_MSG(cyclical_ymd_index >= 0, "Error! Current model time (" + + std::to_string(cyclical_ymd) + + ") is not within " + + "Tracer time period.\n"); - tracer_data.offset_time_index_ = cyclical_ymd_index; + tracer_data.offset_time_index_ = cyclical_ymd_index; } scorpio::release_file(trace_data_file); - tracer_data.file_type = tracer_file_type; - tracer_data.nlevs_data = nlevs_data; - tracer_data.ncols_data = ncols_data; + tracer_data.file_type = tracer_file_type; + tracer_data.nlevs_data = nlevs_data; + tracer_data.ncols_data = ncols_data; tracer_data.has_altitude_ = has_altitude; - } inline std::shared_ptr create_horiz_remapper( const std::shared_ptr &model_grid, const std::string &trace_data_file, const std::string &map_file, - const std::vector &var_names, - TracerData &tracer_data) { + const std::vector &var_names, TracerData &tracer_data) { using namespace ShortFieldTagsNames; // We could use model_grid directly if using same num levels, // but since shallow clones are cheap, we may as well do it (less lines of @@ -528,8 +473,7 @@ inline void update_tracer_data_from_file( } // update_tracer_data_from_file inline void update_tracer_timestate( std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, - AbstractRemapper &tracer_horiz_interp, - TracerTimeState &time_state, + AbstractRemapper &tracer_horiz_interp, TracerTimeState &time_state, TracerData &data_tracer) { // Now we check if we have to update the data that changes monthly // NOTE: This means that SPA assumes monthly data to update. Not @@ -537,8 +481,8 @@ inline void update_tracer_timestate( const auto month = ts.get_month() - 1; // Make it 0-based if(month != time_state.current_month) { const auto tracer_data = data_tracer.data; - const int nvars = data_tracer.nvars_; - const auto ps = data_tracer.ps; + const int nvars = data_tracer.nvars_; + const auto ps = data_tracer.ps; // Update the SPA time state information time_state.current_month = month; @@ -550,12 +494,11 @@ inline void update_tracer_timestate( // Copy spa_end'data into spa_beg'data, and read in the new spa_end for(int ivar = 0; ivar < nvars; ++ivar) { Kokkos::deep_copy(tracer_data[TracerDataIndex::BEG][ivar], - tracer_data[TracerDataIndex::END][ivar]); + tracer_data[TracerDataIndex::END][ivar]); } if(data_tracer.file_type == FORMULA_PS) { - Kokkos::deep_copy(ps[TracerDataIndex::BEG], - ps[TracerDataIndex::END]); + Kokkos::deep_copy(ps[TracerDataIndex::BEG], ps[TracerDataIndex::END]); } // Following SPA to time-interpolate data in MAM4xx @@ -568,7 +511,8 @@ inline void update_tracer_timestate( // values // to be assigned. A timestep greater than a month is very unlikely // so we will proceed. - int next_month = data_tracer.offset_time_index_ + (time_state.current_month + 1) % 12; + int next_month = + data_tracer.offset_time_index_ + (time_state.current_month + 1) % 12; update_tracer_data_from_file(scorpio_reader, next_month, tracer_horiz_interp, data_tracer); } @@ -586,10 +530,10 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, auto &delta_t = time_state.days_this_month; // We can ||ize over columns as well as over variables and bands - const auto data = data_tracer.data; + const auto data = data_tracer.data; const auto file_type = data_tracer.file_type; - const auto ps = data_tracer.ps; + const auto ps = data_tracer.ps; const int num_vars = data_tracer.nvars_; const int ncol = data_tracer.ncol_; @@ -633,7 +577,7 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, if(ivar == 1 && file_type == FORMULA_PS) { ps[TracerDataIndex::OUT](icol) = linear_interp(ps[TracerDataIndex::BEG](icol), - ps[TracerDataIndex::END](icol), delta_t_fraction); + ps[TracerDataIndex::END](icol), delta_t_fraction); } }); Kokkos::fence(); @@ -658,74 +602,43 @@ inline void compute_source_pressure_levels(const view_1d &ps_src, }); } // compute_source_pressure_levels - inline void perform_vertical_interpolation(const view_2d &p_src_c, const const_view_2d &p_tgt_c, const TracerData &input, - const view_2d output[]) - { + const view_2d output[]) { // At this stage, begin/end must have the same horiz dimensions EKAT_REQUIRE(input.ncol_ == int(output[0].extent(0))); - const int ncol = input.ncol_; - const int levsiz =input.nlev_; - const int pver = mam4::nlev; + const int ncol = input.ncol_; + const int levsiz = input.nlev_; + const int pver = mam4::nlev; const int num_vars = input.nvars_; + // FIXME:delete work[ivar] const auto work = input.work_vert_inter; - EKAT_REQUIRE(work[num_vars-1].data() != 0); + EKAT_REQUIRE(work[num_vars - 1].data() != 0); // vert_interp is serial in col and lev. - const auto policy_setup = ESU::get_default_team_policy(num_vars, 1); - const auto data =input.data; + const int outer_iters = ncol * num_vars; + const auto policy_setup = ESU::get_default_team_policy(outer_iters, pver); + const auto data = input.data; Kokkos::parallel_for( "vert_interp", policy_setup, - KOKKOS_LAMBDA(typename LIV::MemberType const &team) - { - const int ivar = team.league_rank(); - vert_interp(ncol, levsiz, pver, p_src_c, - p_tgt_c, data[TracerDataIndex::OUT][ivar], - output[ivar], - // work array - work[ivar]); - }); - } - -// rebin is a port from: -// https://github.com/eagles-project/e3sm_mam4_refactor/blob/ee556e13762e41a82cb70a240c54dc1b1e313621/components/eam/src/chemistry/utils/mo_util.F90#L12 -KOKKOS_INLINE_FUNCTION -void rebin(int nsrc, int ntrg, const const_view_1d &src_x, - const Real trg_x[], const view_1d &src, const view_1d &trg) { - for(int i = 0; i < ntrg; ++i) { - Real tl = trg_x[i]; - if(tl < src_x(nsrc)) { - int sil = 0; - for(; sil <= nsrc; ++sil) { - if(tl <= src_x(sil)) { - break; - } - } - Real tu = trg_x[i + 1]; - int siu = 0; - for(; siu <= nsrc; ++siu) { - if(tu <= src_x(siu)) { - break; - } - } - Real y = 0.0; - sil = haero::max(sil, 1); - siu = haero::min(siu, nsrc); - for(int si = sil; si <= siu; ++si) { - int si1 = si - 1; - Real sl = haero::max(tl, src_x(si1)); - Real su = haero::min(tu, src_x(si)); - y += (su - sl) * src(si1); - } - trg(i) = y / (trg_x[i + 1] - trg_x[i]); - } else { - trg(i) = 0.0; - } - } -} // rebin + KOKKOS_LAMBDA(typename LIV::MemberType const &team) { + // The policy is over ncols*num_vars, so retrieve icol/ivar + const int icol = team.league_rank() / num_vars; + const int ivar = team.league_rank() % num_vars; + const auto pin_at_icol = ekat::subview(p_src_c, icol); + const auto pmid_at_icol = ekat::subview(p_tgt_c, icol); + const auto datain = data[TracerDataIndex::OUT][ivar]; + const auto datain_at_icol = ekat::subview(datain, icol); + const auto dataout = output[ivar]; + const auto dataout_at_icol = ekat::subview(dataout, icol); + + mam4::vertical_interpolation::vert_interp( + team, levsiz, pver, pin_at_icol, pmid_at_icol, datain_at_icol, + dataout_at_icol); + }); +} inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const const_view_2d &zi, @@ -747,7 +660,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const int nsrc = input.nlev_; constexpr int pver = mam4::nlev; const int pverp = pver + 1; - const auto data = input.data; + const auto data = input.data; Kokkos::parallel_for( "tracer_vert_interp_loop", policy_interp, @@ -757,7 +670,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const auto src = ekat::subview(data[TracerDataIndex::OUT][ivar], icol); const auto trg = ekat::subview(output[ivar], icol); - + // FIXME: Try to avoid copy of trg_x by modifying rebin // trg_x Real trg_x[pver + 1]; // I am trying to do this: @@ -766,27 +679,22 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, trg_x[pverp - i - 1] = m2km * zi(icol, i); } team.team_barrier(); - - rebin(nsrc, ntrg, src_x, trg_x, src, trg); + mam4::vertical_interpolation::rebin(team, nsrc, ntrg, src_x, trg_x, src, + trg); }); } inline void advance_tracer_data( std::shared_ptr &scorpio_reader, - AbstractRemapper &tracer_horiz_interp, - const util::TimeStamp &ts, - TracerTimeState &time_state, - TracerData &data_tracer, - const const_view_2d &p_tgt, - const const_view_2d &zi_tgt, + AbstractRemapper &tracer_horiz_interp, const util::TimeStamp &ts, + TracerTimeState &time_state, TracerData &data_tracer, + const const_view_2d &p_tgt, const const_view_2d &zi_tgt, const view_2d output[]) { /* Update the TracerTimeState to reflect the current time, note the addition * of dt */ time_state.t_now = ts.frac_of_year_in_days(); /* Update time state and if the month has changed, update the data.*/ - update_tracer_timestate(scorpio_reader, ts, - tracer_horiz_interp, - time_state, + update_tracer_timestate(scorpio_reader, ts, tracer_horiz_interp, time_state, data_tracer); // Step 1. Perform time interpolation perform_time_interpolation(time_state, data_tracer); @@ -794,16 +702,17 @@ inline void advance_tracer_data( if(data_tracer.file_type == FORMULA_PS) { // Step 2. Compute source pressure levels const auto ps = data_tracer.ps[TracerDataIndex::OUT]; - compute_source_pressure_levels(ps, data_tracer.p_src_, - data_tracer.hyam, data_tracer.hybm); + compute_source_pressure_levels(ps, data_tracer.p_src_, data_tracer.hyam, + data_tracer.hybm); } // Step 3. Perform vertical interpolation - if(data_tracer.file_type == FORMULA_PS || - data_tracer.file_type == ZONAL) { - perform_vertical_interpolation(data_tracer.p_src_, p_tgt, data_tracer, output); + if(data_tracer.file_type == FORMULA_PS || data_tracer.file_type == ZONAL) { + perform_vertical_interpolation(data_tracer.p_src_, p_tgt, data_tracer, + output); } else if(data_tracer.file_type == VERT_EMISSION) { - perform_vertical_interpolation(data_tracer.altitude_int_, zi_tgt, data_tracer, output); + perform_vertical_interpolation(data_tracer.altitude_int_, zi_tgt, + data_tracer, output); } } // advance_tracer_data diff --git a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp b/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp index 39a886744f4..0f7c5138ae5 100644 --- a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp +++ b/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp @@ -7,1158 +7,1479 @@ namespace scream::impl { -#define MAX_FILENAME_LEN 256 - using namespace mam4; // number of constituents in gas chemistry "work arrays" -KOKKOS_INLINE_FUNCTION -constexpr int gas_pcnst() { - constexpr int gas_pcnst_ = mam4::gas_chemistry::gas_pcnst; - return gas_pcnst_; -} +using mam4::gas_chemistry::gas_pcnst; -// number of aerosol/gas species tendencies -KOKKOS_INLINE_FUNCTION -constexpr int nqtendbb() { return 4; } +// number of modes in modal configuration +constexpr int num_modes = AeroConfig::num_modes(); -// MAM4 aerosol microphysics configuration data -struct AmicPhysConfig { - // these switches activate various aerosol microphysics processes - bool do_cond; // condensation (a.k.a gas-aerosol exchange) - bool do_rename; // mode "renaming" - bool do_newnuc; // gas -> aerosol nucleation - bool do_coag; // aerosol coagulation +// number of gases +constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - // configurations for specific aerosol microphysics - mam4::GasAerExchProcess::ProcessConfig condensation; - mam4::NucleationProcess::ProcessConfig nucleation; +// number of aerosol species +constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); - // controls treatment of h2so4 condensation in mam_gasaerexch_1subarea - // 1 = sequential calc. of gas-chem prod then condensation loss - // 2 = simultaneous calc. of gas-chem prod and condensation loss - int gaexch_h2so4_uptake_optaa; +//----------------------------------------------------------------------------- +// Indices for amicphys +//----------------------------------------------------------------------------- - // controls how nucleation interprets h2so4 concentrations - int newnuc_h2so4_conc_optaa; -}; +// Indices of aerosol number for the arrays dimensioned gas_pcnst +KOKKOS_INLINE_FUNCTION int numptr_amode_gas_pcnst(const int mode) { + static constexpr int numptr_amode_gas_pcnst_[num_modes] = {13, 18, 26, 30}; + return numptr_amode_gas_pcnst_[mode]; +} -namespace { +// Indices of aerosol mass for the arrays dimensioned gas_pcnst +KOKKOS_INLINE_FUNCTION int lmassptr_amode_gas_pcnst(const int aero_id, + const int mode) { + static constexpr int lmassptr_amode_gas_pcnst_[num_aerosol_ids][num_modes] = { + {6, 14, 19, 27}, {7, 15, 20, 28}, {8, 16, 21, 29}, {9, 17, 22, -6}, + {10, -6, 23, -6}, {11, -6, 24, -6}, {12, -6, 25, -6}}; + return lmassptr_amode_gas_pcnst_[aero_id][mode]; +} +KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_nul() { return 0; } +KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_gas() { return 1; } +KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_aer() { return 2; } +KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_num() { return 3; } +KOKKOS_INLINE_FUNCTION int lmapcc_all(const int index) { + static constexpr int lmapcc_all_[gas_pcnst] = { + lmapcc_val_nul(), lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_nul(), + lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), + lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num()}; + return lmapcc_all_[index]; +} + +// Where lmapcc_val_gas are defined in lmapcc_all +KOKKOS_INLINE_FUNCTION int lmap_gas(const int mode) { + static constexpr int lmap_gas_[num_modes] = {5, 2}; + return lmap_gas_[mode]; +} + +// number of aerosol/gas species tendencies +KOKKOS_INLINE_FUNCTION +constexpr int nqtendbb() { return 4; } -KOKKOS_INLINE_FUNCTION constexpr int nqtendaa() { return 5; } +KOKKOS_INLINE_FUNCTION constexpr int nqtendaa() { return 4; } KOKKOS_INLINE_FUNCTION constexpr int nqqcwtendaa() { return 1; } -KOKKOS_INLINE_FUNCTION constexpr int nqqcwtendbb() { return 1; } KOKKOS_INLINE_FUNCTION constexpr int iqtend_cond() { return 0; } KOKKOS_INLINE_FUNCTION constexpr int iqtend_rnam() { return 1; } KOKKOS_INLINE_FUNCTION constexpr int iqtend_nnuc() { return 2; } KOKKOS_INLINE_FUNCTION constexpr int iqtend_coag() { return 3; } KOKKOS_INLINE_FUNCTION constexpr int iqtend_cond_only() { return 4; } KOKKOS_INLINE_FUNCTION constexpr int iqqcwtend_rnam() { return 0; } -KOKKOS_INLINE_FUNCTION constexpr int maxsubarea() { return 2; } +KOKKOS_INLINE_FUNCTION constexpr int n_agepair() { return 1; } -// conversion factors -KOKKOS_INLINE_FUNCTION Real fcvt_gas(int gas_id) { - static const Real fcvt_gas_[AeroConfig::num_gas_ids()] = {1, 1, 1}; - return fcvt_gas_[gas_id]; -} -KOKKOS_INLINE_FUNCTION Real fcvt_aer(int aero_id) { - static const Real fcvt_aer_[AeroConfig::num_aerosol_ids()] = {1, 1, 1, 1, - 1, 1, 1}; - return fcvt_aer_[aero_id]; -} +// In EAMv2, subarea can take 3 values (0,1 and 2), therefore +// length of the maxsubarea is 3 +KOKKOS_INLINE_FUNCTION constexpr int maxsubarea() { return 3; } + +// number of gases used in aerosol microphysics (soag and h2so4) +KOKKOS_INLINE_FUNCTION constexpr int max_gas() { return 2; } + +// Index for h2so4 and nh3 +constexpr int igas_h2so4 = 1; // FIXME: This can change with modal model +constexpr int igas_nh3 = -1; // FIXME: This can change with modal model // leave number mix-ratios unchanged (#/kmol-air) -KOKKOS_INLINE_FUNCTION Real fcvt_num() { return 1.0; } +KOKKOS_INLINE_FUNCTION Real fcvt_num() { return 1; } + // factor for converting aerosol water mix-ratios from (kg/kg) to (mol/mol) -KOKKOS_INLINE_FUNCTION Real fcvt_wtr() { return 1.0; } +KOKKOS_INLINE_FUNCTION Real fcvt_wtr() { + return haero::Constants::molec_weight_dry_air / + haero::Constants::molec_weight_h2o; +} -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_nul() { return 0; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_gas() { return 1; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_aer() { return 2; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_num() { return 3; } -KOKKOS_INLINE_FUNCTION int lmapcc_all(int index) { - static const int lmapcc_all_[gas_pcnst()] = { - lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_nul(), lmapcc_val_nul(), - lmapcc_val_gas(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num()}; - return lmapcc_all_[index]; +// Returns index of aerosol numbers in gas_pcnst array +KOKKOS_INLINE_FUNCTION int lmap_num(const int mode) { + return numptr_amode_gas_pcnst(mode); } -// Where lmapcc_val_num are defined in lmapcc_all -KOKKOS_INLINE_FUNCTION int numptr_amode(int mode) { - static const int numptr_amode_[AeroConfig::num_modes()] = {12, 17, 25, 29}; - return numptr_amode_[mode]; +// Returns index of aerosol numbers in gas_pcnst array +KOKKOS_INLINE_FUNCTION int lmap_numcw(const int mode) { + return numptr_amode_gas_pcnst(mode); +} +// aerosol mapping for aerosol microphysics +// NOTE: it is different from "lmassptr_amode_gas_pcnst" as +// amicphys adds aerosol species in a special order that is different from +// lmassptr_amode_gas_pcnst +KOKKOS_INLINE_FUNCTION int lmap_aer(const int iaer, const int mode) { + static constexpr int lmap_aer_[num_aerosol_ids][num_modes] = { + {8, 15, 24, -1}, {6, 14, 21, -1}, {7, -1, 23, 27}, {9, -1, 22, 28}, + {11, 16, 20, -1}, {10, -1, 19, -1}, {12, 17, 25, 29}, + }; + return lmap_aer_[iaer][mode]; } -// Where lmapcc_val_gas are defined in lmapcc_all -KOKKOS_INLINE_FUNCTION int lmap_gas(int mode) { - static const int lmap_gas_[AeroConfig::num_modes()] = {4, 1}; - return lmap_gas_[mode]; +KOKKOS_INLINE_FUNCTION int lmap_aercw(const int iaer, const int mode) { + return lmap_aer(iaer, mode); +} + +constexpr Real mass_2_vol[num_aerosol_ids] = {0.15, + 6.4971751412429377e-002, + 0.15, + 7.0588235294117650e-003, + 3.0789473684210526e-002, + 5.1923076923076926e-002, + 156.20986883198000}; + +// conversion factor for aerosols +// NOTE: The following array has a special order to match amicphys +KOKKOS_INLINE_FUNCTION Real fcvt_aer(const int iaer) { + static constexpr Real fcvt_aer_[num_aerosol_ids] = { + 8.000000000000000E-002, 1, 8.000000000000000E-002, 1, 1, 1, 1}; + return fcvt_aer_[iaer]; } -// Where lmapcc_val_aer are defined in lmapcc_all -KOKKOS_INLINE_FUNCTION int lmassptr_amode(int aero_id, int mode) { - static const int lmassptr_amode_[AeroConfig::num_aerosol_ids()] - [AeroConfig::num_modes()] = { - {5, 13, 18, 26}, {6, 14, 19, 27}, - {7, 15, 20, 28}, {8, 16, 21, -6}, - {9, -6, 22, -6}, {10, -6, 23, -6}, - {11, -6, 24, -6}}; - return lmassptr_amode_[aero_id][mode]; + +// Number of differently tagged secondary-organic aerosol species +KOKKOS_INLINE_FUNCTION constexpr int nsoa() { return 1; } + +// conversion factor for gases +KOKKOS_INLINE_FUNCTION Real fcvt_gas(const int gas_id) { + // mw to use for soa + // BAD CONSTANTS + constexpr Real mwuse_soa = 150; + // molecular weight of the gas + Real mw_gas = mam4::gas_chemistry::adv_mass[lmap_gas(gas_id)]; + // denominator + Real denom = mw_gas; + // special case for soa + if(gas_id < nsoa()) denom = mwuse_soa; + return mw_gas / denom; } +//-------------------------------------------------------------------------------- +// Utility functions +//-------------------------------------------------------------------------------- + KOKKOS_INLINE_FUNCTION -void subarea_partition_factors( - const Real - q_int_cell_avg, // in grid cell mean interstitial aerosol mixing ratio - const Real - q_cbn_cell_avg, // in grid cell mean cloud-borne aerosol mixing ratio - const Real fcldy, // in cloudy fraction of the grid cell - const Real fclea, // in clear fraction of the grid cell - Real &part_fac_q_int_clea, // out - Real &part_fac_q_int_cldy) // out -{ - // Calculate mixing ratios of each subarea +void copy_1d_array(const int arr_len, const Real (&arr_in)[arr_len], // in + Real (&arr_out)[arr_len]) { // out + for(int ii = 0; ii < arr_len; ++ii) { + arr_out[ii] = arr_in[ii]; + } +} - // cloud-borne, cloudy subarea - const Real tmp_q_cbn_cldy = q_cbn_cell_avg / fcldy; - // interstitial, cloudy subarea - const Real tmp_q_int_cldy = - haero::max(0.0, ((q_int_cell_avg + q_cbn_cell_avg) - tmp_q_cbn_cldy)); - // interstitial, clear subarea - const Real tmp_q_int_clea = (q_int_cell_avg - fcldy * tmp_q_int_cldy) / fclea; - - // Calculate the corresponding paritioning factors for interstitial aerosols - // using the above-derived subarea mixing ratios plus the constraint that - // the cloud fraction weighted average of subarea mean need to match grid box - // mean. - - // *** question *** - // use same part_fac_q_int_clea/cldy for everything ? - // use one for number and one for all masses (based on total mass) ? - // use separate ones for everything ? - // maybe one for number and one for all masses is best, - // because number and mass have different activation fractions - // *** question *** - - Real tmp_aa = haero::max(1.e-35, tmp_q_int_clea * fclea) / - haero::max(1.e-35, q_int_cell_avg); - tmp_aa = haero::max(0.0, haero::min(1.0, tmp_aa)); - - part_fac_q_int_clea = tmp_aa / fclea; - part_fac_q_int_cldy = (1.0 - tmp_aa) / fcldy; +KOKKOS_INLINE_FUNCTION +void copy_2d_array(const int first_dimlen, // in + const int second_dimlen, // in + const Real (&arr_in)[first_dimlen][second_dimlen], // in + Real (&arr_out)[first_dimlen][second_dimlen]) { // out + + for(int ifd = 0; ifd < first_dimlen; ++ifd) { + for(int isd = 0; isd < second_dimlen; ++isd) { + arr_out[ifd][isd] = arr_in[ifd][isd]; + } + } +} +template +KOKKOS_INLINE_FUNCTION void assign_1d_array(const int arr_len, + const DT num, // in + DT *arr_out) { // out + for(int ii = 0; ii < arr_len; ++ii) { + arr_out[ii] = num; + } } KOKKOS_INLINE_FUNCTION -void construct_subareas_1gridcell( - const Real cld, // in - const Real relhumgcm, // in - const Real q_pregaschem[gas_pcnst()], // in q TMRs before - // gas-phase chemistry - const Real q_precldchem[gas_pcnst()], // in q TMRs before - // cloud chemistry - const Real qqcw_precldchem[gas_pcnst()], // in qqcw TMRs before - // cloud chemistry - const Real q[gas_pcnst()], // in current tracer mixing ratios (TMRs) - // *** MUST BE #/kmol-air for number - // *** MUST BE mol/mol-air for mass - const Real qqcw[gas_pcnst()], // in like q but for - // cloud-borner tracers - int &nsubarea, // out - int &ncldy_subarea, // out - int &jclea, // out - int &jcldy, // out - bool iscldy_subarea[maxsubarea()], // out - Real afracsub[maxsubarea()], // out - Real relhumsub[maxsubarea()], // out - Real qsub1[gas_pcnst()][maxsubarea()], // out interstitial - Real qsub2[gas_pcnst()][maxsubarea()], // out interstitial - Real qsub3[gas_pcnst()][maxsubarea()], // out interstitial - Real qqcwsub1[gas_pcnst()][maxsubarea()], // out cloud-borne - Real qqcwsub2[gas_pcnst()][maxsubarea()], // out cloud-borne - Real qqcwsub3[gas_pcnst()][maxsubarea()], // outcloud-borne - Real - qaerwatsub3[AeroConfig::num_modes()] - [maxsubarea()], // out aerosol water mixing ratios (mol/mol) - Real qaerwat[AeroConfig::num_modes()] // in aerosol water mixing ratio - // (kg/kg, NOT mol/mol) -) { - static constexpr int num_modes = AeroConfig::num_modes(); - // cloud chemistry is only on when cld(i,k) >= 1.0e-5_wp - // it may be that the macrophysics has a higher threshold that this - const Real fcld_locutoff = 1.0e-5; - const Real fcld_hicutoff = 0.999; - - // qgcmN and qqcwgcmN (N=1:4) are grid-cell mean tracer mixing ratios (TMRs, - // mol/mol or #/kmol) - // N=1 - before gas-phase chemistry - // N=2 - before cloud chemistry - // N=3 - incoming values (before gas-aerosol exchange, newnuc, coag) - // qgcm1, qgcm2, qgcm3 - // qqcwgcm2, qqcwgcm3 - // qaerwatgcm3 ! aerosol water mixing ratios (mol/mol) - - // -------------------------------------------------------------------------------------- - // Determine the number of sub-areas, their fractional areas, and relative - // humidities - // -------------------------------------------------------------------------------------- - // if cloud fraction ~= 0, the grid-cell has a single clear sub-area - // (nsubarea = 1) if cloud fraction ~= 1, the grid-cell has a single cloudy - // sub-area (nsubarea = 1) otherwise, the grid-cell has a - // clear and a cloudy sub-area (nsubarea = 2) - - Real zfcldy = 0; - nsubarea = 0; - ncldy_subarea = 0; - jclea = 0; - jcldy = 0; +void assign_2d_array(const int first_dimlen, // in + const int second_dimlen, // in + const Real num, // in + Real (&arr_out)[first_dimlen][second_dimlen]) { // out + for(int ifd = 0; ifd < first_dimlen; ++ifd) { + for(int isd = 0; isd < second_dimlen; ++isd) { + arr_out[ifd][isd] = num; + } + } +} +// copy 3d arrays +KOKKOS_INLINE_FUNCTION +void assign_3d_array( + const int first_dimlen, // in + const int second_dimlen, // in + const int third_dimlen, // in + const Real num, // in + Real (&arr_out)[first_dimlen][second_dimlen][third_dimlen]) { // out + for(int ifd = 0; ifd < first_dimlen; ++ifd) { + for(int isd = 0; isd < second_dimlen; ++isd) { + for(int itd = 0; itd < third_dimlen; ++itd) { + arr_out[ifd][isd][itd] = num; + } + } + } +} + +//-------------------------------------------------------------------------------- +// Configuration settings +//-------------------------------------------------------------------------------- + +// MAM4 aerosol microphysics configuration data +struct AmicPhysConfig { + // these switches activate various aerosol microphysics processes + bool do_cond; // condensation (a.k.a gas-aerosol exchange) + bool do_rename; // mode "renaming" + bool do_newnuc; // gas -> aerosol nucleation + bool do_coag; // aerosol coagulation + + // controls treatment of h2so4 condensation in mam_gasaerexch_1subarea + // 1 = sequential calc. of gas-chem prod then condensation loss + // 2 = simultaneous calc. of gas-chem prod and condensation loss + int gaexch_h2so4_uptake_optaa; + + // controls how nucleation interprets h2so4 concentrations + int newnuc_h2so4_conc_optaa; +}; + +namespace { + +KOKKOS_INLINE_FUNCTION +void setup_subareas(const Real cld, // in + int &nsubarea, int &ncldy_subarea, // out + int &jclea, int &jcldy, // out + bool (&iscldy_subarea)[(maxsubarea())], // out + Real (&afracsub)[maxsubarea()], // out + Real &fclea, Real &fcldy) // out +{ + //-------------------------------------------------------------------------------------- + // Purpose: Determine the number of sub-areas and their fractional areas. + // Assign values to some bookkeeping variables. + //-------------------------------------------------------------------------------------- + + // cld: cloud fraction in the grid cell [unitless] + // nsubarea: total # of subareas to do calculations for + // ncldy_subarea: total # of cloudy subareas + // jclea, jcldy: indices of the clear and cloudy subareas + // iscldy_subarea(maxsubarea): whether a subarea is cloudy + // afracsub(maxsubarea): area fraction of each active subarea[unitless] + // fclea, fcldy: area fraction of clear/cloudy subarea [unitless] + + // BAD CONSTANT + // Cloud chemistry is only active when cld(i,kk) >= 1.0e-5 + // It may be that the macrophysics has a higher threshold than this + constexpr Real fcld_locutoff = 1.0e-5; + + // BAD CONSTANT + // Grid cells with cloud fraction larger than this cutoff is considered to be + // overcast + constexpr Real fcld_hicutoff = 0.999; + + // if cloud fraction ~= 0, the grid-cell has a single clear sub-area + // (nsubarea = 1) if cloud fraction ~= 1, the grid-cell has a single cloudy + // sub-area (nsubarea = 1) otherwise, the grid-cell has a clear and a cloudy + // sub-area (nsubarea = 2) if(cld < fcld_locutoff) { - nsubarea = 1; - jclea = 1; + fcldy = 0; + nsubarea = 1; + ncldy_subarea = 0; + jclea = 1; + jcldy = 0; } else if(cld > fcld_hicutoff) { - zfcldy = 1.0; + fcldy = 1; nsubarea = 1; ncldy_subarea = 1; + jclea = 0; jcldy = 1; } else { - zfcldy = cld; + fcldy = cld; nsubarea = 2; ncldy_subarea = 1; jclea = 1; jcldy = 2; } - const Real zfclea = 1.0 - zfcldy; - for(int i = 0; i < maxsubarea(); ++i) iscldy_subarea[i] = false; - if(jcldy > 0) iscldy_subarea[jcldy - 1] = true; - for(int i = 0; i < maxsubarea(); ++i) afracsub[i] = 0.0; - if(jclea > 0) afracsub[jclea - 1] = zfclea; - if(jcldy > 0) afracsub[jcldy - 1] = zfcldy; + fclea = 1.0 - fcldy; + + // Set up a logical array to indicate whether the subareas are clear or cloudy + // and init area fraction array + for(int jsub = 0; jsub < maxsubarea(); ++jsub) { + iscldy_subarea[jsub] = false; + afracsub[jsub] = 0; + } + + // jcldy>0 can be 1 or 2, so iscldy_subarea(1) or iscldy_subarea(2) is true + if(jcldy > 0) iscldy_subarea[jcldy] = true; + // Save the area fractions to an array + // jclea can only be 1 if jclea > 0, so afracsub (1) is set to fclea + if(jclea > 0) afracsub[jclea] = fclea; + // jcldy can be 1 or 2, so afracsub(1) or afracsub(2) is set to fcldy + if(jcldy > 0) afracsub[jcldy] = fcldy; + +} // setup_subareas + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_rh(const int &ncldy_subarea, const int &jclea, // in + const int &jcldy, // in + const Real (&afracsub)[maxsubarea()], // in + const Real &relhumgcm, // in + Real (&relhumsub)[maxsubarea()]) // out +{ + //---------------------------------------------------------------------------- + // Purpose: Set relative humidity in subareas. + //---------------------------------------------------------------------------- + + // ncldy_subarea :# of cloudy subareas + // jclea, jcldy :indices of clear and cloudy subareas + // afracsub(maxsubarea) :area fraction in subareas [unitless] + // relhumgcm :grid cell mean relative humidity [unitless] + // relhumsub(maxsubarea): relative humidity in subareas [unitless] - // cldy_rh_sameas_clear is just to match mam_refactor. Compiler should - // optimize away. - const int cldy_rh_sameas_clear = 0; if(ncldy_subarea <= 0) { - for(int i = 0; i < maxsubarea(); ++i) relhumsub[i] = relhumgcm; - } else if(cldy_rh_sameas_clear > 0) { - for(int i = 0; i < maxsubarea(); ++i) relhumsub[i] = relhumgcm; + // Entire grid cell is cloud-free. RH in subarea = grid cell mean. + // This is clear cell, rehumsub(0),rehumsub(1) and rehumsub(3) are relhumgcm + for(int jsub = 0; jsub < maxsubarea(); ++jsub) relhumsub[jsub] = relhumgcm; } else { - if(jcldy > 0) { - relhumsub[jcldy - 1] = 1.0; - if(jclea > 0) { - const Real tmpa = - (relhumgcm - afracsub[jcldy - 1]) / afracsub[jclea - 1]; - relhumsub[jclea - 1] = haero::max(0.0, haero::min(1.0, tmpa)); - } + // Grid cell has a cloudy subarea. Set RH in that part to 1.0. + // jcldy can be 1 or 2 here. + // If jcldy is 1: relhumsub[1] is 1.0 (fully cloudy cell) + // if jcldy is 2: relhumsub[2] is 1.0. In this case jclea is >0, + // so relhumsub[1] is set in if condition below + relhumsub[jcldy] = 1; + + // If the grid cell also has a clear portion, back out the subarea RH from + // the grid-cell mean RH and cloud fraction. + if(jclea > 0) { + // jclea is > 0 only for partly cloudy cell. In this case + // jclea is 1, so relhumsub[1] is set here. + Real relhum_tmp = (relhumgcm - afracsub[jcldy]) / afracsub[jclea]; + relhumsub[jclea] = mam4::utils::min_max_bound(0, 1, relhum_tmp); } } +} // set_subarea_rh - // ---------------------------------------------------------------------------- - // Copy grid cell mean mixing ratios. - // These values, together with cloud fraction and a few assumptions, are used - // in the remainder of the subroutine to calculate the sub-area mean mixing - // ratios. - // ---------------------------------------------------------------------------- - // Interstitial aerosols - Real qgcm1[gas_pcnst()], qgcm2[gas_pcnst()], qgcm3[gas_pcnst()]; - for(int i = 0; i < gas_pcnst(); ++i) { - qgcm1[i] = haero::max(0.0, q_pregaschem[i]); - qgcm2[i] = haero::max(0.0, q_precldchem[i]); - qgcm3[i] = haero::max(0.0, q[i]); - } +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- - // Cloud-borne aerosols - Real qqcwgcm2[gas_pcnst()], qqcwgcm3[gas_pcnst()]; - for(int i = 0; i < gas_pcnst(); ++i) { - qqcwgcm2[i] = haero::max(0.0, qqcw_precldchem[i]); - qqcwgcm3[i] = haero::max(0.0, qqcw[i]); +KOKKOS_INLINE_FUNCTION +void compute_qsub_from_gcm_and_qsub_of_other_subarea( + const bool (&lcompute)[gas_pcnst], const Real &f_a, // in + const Real &f_b, // in + const Real (&qgcm)[gas_pcnst], // in + const int &jclea, const int &jcldy, // in + Real (&qsub_a)[gas_pcnst][maxsubarea()], // inout + Real (&qsub_b)[gas_pcnst][maxsubarea()]) // inout +{ + //----------------------------------------------------------------------------------------- + // Purpose: Calculate the value of qsub_b assuming qgcm is a weighted average + // defined as + // qgcm = f_a*qsub_a + f_b*qsub_b. + //----------------------------------------------------------------------------------------- + + // f_a, f_b // area fractions [unitless] of subareas + // qgcm(ncnst) // grid cell mean (known) + // qsub_a(ncnst) // value in subarea A (known, but might get adjusted) + // qsub_b(ncnst) // value in subarea B (to be calculated here) + + // Here we populate qsub for subarea index 2 (i.e. jcldy is 2 here) + // and adjust subarea index 1(i.e., jclea is 1 here) if needed. + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + if(lcompute[icnst]) { + // Calculate qsub_b + EKAT_KERNEL_ASSERT_MSG( + f_b != 0, + "Error! compute_qsub_from_gcm_and_qsub_of_other_subarea - f_b is " + "zero\n"); + qsub_b[icnst][jcldy] = (qgcm[icnst] - f_a * qsub_a[icnst][jclea]) / f_b; + // Check that this does not produce a negative value. + // If so, set qsub_b to zero and adjust the value of qsub_a. + if(qsub_b[icnst][jcldy] < 0) { + qsub_b[icnst][jcldy] = 0; + EKAT_KERNEL_ASSERT_MSG( + f_a != 0, + "Error! compute_qsub_from_gcm_and_qsub_of_other_subarea - f_a is " + "zero\n"); + qsub_a[icnst][jclea] = qgcm[icnst] / f_a; + } + } } +} // compute_qsub_from_gcm_and_qsub_of_other_subarea - // aerosol water - Real qaerwatgcm3[num_modes] = {}; - for(int i = 0; i < num_modes; ++i) { - qaerwatgcm3[i] = haero::max(0.0, qaerwat[i]); +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_qnumb_for_cldbrn_aerosols( + const int &jclea, const int &jcldy, const Real &fcldy, // in + const Real (&qqcwgcm)[gas_pcnst], // in + Real (&qqcwsub)[gas_pcnst][maxsubarea()]) // inout +{ + //----------------------------------------------------------------------------------------- + // Purpose: Set the number mixing ratios of cloud-borne aerosols in subareas: + // - zero in clear air; + // - grid-cell-mean divided by cloud-fraction in the cloudy subarea. + // This is done for all lognormal modes. + //----------------------------------------------------------------------------------------- + + // jclea, jcldy : indices of subareas + // fcldy : area fraction [unitless] of the cloudy subarea + // qqcwgcm(ncnst) : grid cell mean (unit does not matter for this + // subr.) + // qqcwsub(ncnst,maxsubarea) : values in subareas (unit does not matter + // for this subr.) + + //---------------------------------------------------------------- + // Here jclea ==1 and jcldy==2 + for(int imode = 0; imode < num_modes; ++imode) { + const int icnst = numptr_amode_gas_pcnst(imode); + qqcwsub[icnst][jclea] = 0; + EKAT_KERNEL_ASSERT_MSG( + fcldy != 0, + "Error! set_subarea_qnumb_for_cldbrn_aerosols - fcldy is " + "zero\n"); + qqcwsub[icnst][jcldy] = qqcwgcm[icnst] / fcldy; + //---------------------------------------------------------------- } - // ---------------------------------------------------------------------------- - // Initialize the subarea mean mixing ratios - // ---------------------------------------------------------------------------- - { - const int n = haero::min(maxsubarea(), nsubarea + 1); - for(int i = 0; i < n; ++i) { - for(int j = 0; j < gas_pcnst(); ++j) { - qsub1[j][i] = 0.0; - qsub2[j][i] = 0.0; - qsub3[j][i] = 0.0; - qqcwsub1[j][i] = 0.0; - qqcwsub2[j][i] = 0.0; - qqcwsub3[j][i] = 0.0; - } - for(int j = 0; j < num_modes; ++j) { - qaerwatsub3[j][i] = 0.0; - } +} // set_subarea_qnumb_for_cldbrn_aerosols + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_qmass_for_cldbrn_aerosols( + const int &jclea, const int &jcldy, // in + const Real &fcldy, // in + const Real (&qqcwgcm)[gas_pcnst], // in + Real (&qqcwsub)[gas_pcnst][maxsubarea()]) // inout +{ + //----------------------------------------------------------------------------------------- + // Purpose: Set the mass mixing ratios of cloud-borne aerosols in subareas: + // - zero in clear air; + // - grid-cell-mean/cloud-fraction in the cloudy subarea. + // This is done for all lognormal modes and all chemical species. + //----------------------------------------------------------------------------------------- + // jclea, jcldy : subarea indices fcldy : area + // fraction [unitless] of the cloudy subarea + // qqcwgcm(ncnst) : grid cell mean (unit does not matter for this + // subr.) + // qqcwsub(ncnst,maxsubarea) : values in subareas (unit does not matter for + // this subr.) + + //---------------------------------------------------------------- + // Here jclea ==1 and jcldy==2 + + // loop thru all modes + for(int imode = 0; imode < num_modes; ++imode) { + // loop thru all species in a mode + for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { + const int icnst = lmassptr_amode_gas_pcnst(ispec, imode); + + qqcwsub[icnst][jclea] = 0; + EKAT_KERNEL_ASSERT_MSG( + fcldy != 0, + "Error! set_subarea_qmass_for_cldbrn_aerosols - fcldy is " + "zero\n"); + qqcwsub[icnst][jcldy] = qqcwgcm[icnst] / fcldy; + } // ispec - species loop + } // imode - mode loop + //---------------------------------------------------------------- +} // set_subarea_qmass_for_cldbrn_aerosols + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void get_partition_factors(const Real &qgcm_intrst, // in + const Real &qgcm_cldbrn, // in + const Real &fcldy, const Real &fclea, // in + Real &factor_clea, Real &factor_cldy) // out +{ + //------------------------------------------------------------------------------------ + // Purpose: Calculate the partitioning factors for distributing interstitial + // aerosol + // mixing ratios to cloudy and clear subareas in a grid box. + // The partitioning factors depend on the grid cell mean mixing + // ratios of both interstitial and cloud-borne aerosols. + //------------------------------------------------------------------------------------ + + // qgcm_intrst : grid cell mean interstitial aerosol mixing ratio + // qgcm_cldbrn : grid cell mean cloud-borne aerosol mixing ratio + // + // fcldy : cloudy fraction of the grid cell [unitless] + // fclea : clear fraction of the grid cell [unitless] + // + // factor_clea : partitioning factor for clear subarea + // factor_cldy : partitioning factor for cloudy subarea + + // Calculate subarea-mean mixing ratios + + EKAT_KERNEL_ASSERT_MSG(fcldy != 0, + "Error! get_partition_factors - fcldy is " + "zero\n"); + // cloud-borne, cloudy subarea + const Real tmp_q_cldbrn_cldy = qgcm_cldbrn / fcldy; + + // interstitial, cloudy subarea + const Real tmp_q_intrst_cldy = + haero::max(0, ((qgcm_intrst + qgcm_cldbrn) - tmp_q_cldbrn_cldy)); + + EKAT_KERNEL_ASSERT_MSG(fclea != 0, + "Error! get_partition_factors - fclea is " + "zero\n"); + // interstitial, clear subarea + const Real tmp_q_intrst_clea = + (qgcm_intrst - fcldy * tmp_q_intrst_cldy) / fclea; + + // Calculate the corresponding paritioning factors for interstitial + // aerosols using the above-derived subarea-mean mixing ratios plus the + // constraint that the cloud fraction weighted average of subarea mean + // need to match grid box mean. Note that this subroutine is designed for + // partially cloudy grid cells, hence both fclea and fcldy are assumed to + // be nonzero. + + constexpr Real eps = 1.e-35; // BAD CONSTANT + Real clea2gcm_ratio = + haero::max(eps, tmp_q_intrst_clea * fclea) / haero::max(eps, qgcm_intrst); + clea2gcm_ratio = haero::max(0, haero::min(1, clea2gcm_ratio)); + + factor_clea = clea2gcm_ratio / fclea; + factor_cldy = (1 - clea2gcm_ratio) / fcldy; +} // get_partition_factors + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_qnumb_for_intrst_aerosols( + const int &jclea, const int &jcldy, const Real &fclea, // in + const Real &fcldy, const Real (&qgcm)[gas_pcnst], // in + const Real (&qqcwgcm)[gas_pcnst], // in + const Real (&qgcmx)[gas_pcnst], // in + Real (&qsubx)[gas_pcnst][maxsubarea()]) // inout +{ + //----------------------------------------------------------------------------------------- + // Purpose: Set the number mixing ratios of interstitial aerosols in subareas. + // Interstitial aerosols can exist in both cloudy and clear subareas, + // so a grid cell mean needs to be partitioned. Different lognormal + // modes are partitioned differently based on the mode-specific + // number mixing ratios. + //----------------------------------------------------------------------------------------- + + // jclea, jcldy : subarea indices + // fclea, fcldy : area fraction [unitless] of the clear and cloudy subareas + // qgcm (ncnst): grid cell mean, interstitial constituents (unit does not + // matter) + // qqcwgcm(ncnst): grid cell mean, cloud-borne constituents (unit + // does not matter) + + // qgcmx (ncnst): grid cell mean, interstitial constituents (unit does not + // matter) + // qsubx(ncnst,maxsubarea): subarea mixing ratios of interst. constituents + // (unit does not matter as long as they are + // consistent with the grid cell mean values) + + // Note: qgcm and qqcwgcm are used for calculating the patitioning factors. + // qgcmx is the actual grid cell mean that is partitioned into qsubx. + + for(int imode = 0; imode < num_modes; ++imode) { + // calculate partitioning factors + + // grid cell mean of interstitial aerosol mixing ratio of a single mode + const Real qgcm_intrst = qgcm[numptr_amode_gas_pcnst(imode)]; + + // grid cell mean of cloud-borne aerosol mixing ratio of a single mode + const Real qgcm_cldbrn = qqcwgcm[numptr_amode_gas_pcnst(imode)]; + + Real factor_clea; // partitioning factor for clear subarea [unitless] + Real factor_cldy; // partitioning factor for cloudy subarea [unitless] + get_partition_factors(qgcm_intrst, qgcm_cldbrn, fcldy, fclea, // in + factor_clea, factor_cldy); // out + + // apply partitioning factors + const int icnst = numptr_amode_gas_pcnst(imode); + + qsubx[icnst][jclea] = qgcmx[icnst] * factor_clea; + qsubx[icnst][jcldy] = qgcmx[icnst] * factor_cldy; + } // imode + +} // set_subarea_qnumb_for_intrst_aerosols + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_qmass_for_intrst_aerosols( + const int &jclea, const int &jcldy, const Real &fclea, // in + const Real &fcldy, const Real (&qgcm)[gas_pcnst], // in + const Real (&qqcwgcm)[gas_pcnst], // in + const Real (&qgcmx)[gas_pcnst], // in + Real (&qsubx)[gas_pcnst][maxsubarea()]) // inout +{ + //----------------------------------------------------------------------------------------- + // Purpose: Set the mass mixing ratios of interstitial aerosols in subareas. + // Interstitial aerosols can exist in both cloudy and clear subareas, + // so a grid cell mean needs to be partitioned. Different lognormal + // modes are partitioned differently based on the mode-specific + // mixing ratios. All species in the same mode are partitioned the + // same way, consistent with the internal mixing assumption used in + // MAM. + //----------------------------------------------------------------------------------------- + + // jclea, jcldy : subarea indices + // fclea, fcldy : area fraction [unitless] of the clear and cloudy subareas + // qgcm (ncnst) : grid cell mean, interstitial constituents (unit does not + // matter) + // qqcwgcm(ncnst) : grid cell mean, cloud-borne constituents (unit + // does not matter) + + // qgcmx (ncnst) : grid cell mean, interstitial constituents (unit does not + // matter) + // qsubx(ncnst,maxsubarea): subarea mixing ratios of interst. + // constituents(unit does not matter as long as they + // are consistent with the grid cell mean values) + + // Note: qgcm and qqcwgcm are used for calculating the patitioning factors. + // qgcmx is the actual grid cell mean that is partitioned into qsubx. + + for(int imode = 0; imode < num_modes; ++imode) { + // calculcate partitioning factors + + // grid cell mean of interstitial aerosol mixing ratio of a single mode + Real qgcm_intrst = 0; + + // grid cell mean of cloud-borne aerosol mixing ratio of a single mode + Real qgcm_cldbrn = 0; + + // loop thru all species in a mode + for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { + qgcm_intrst = qgcm_intrst + qgcm[lmassptr_amode_gas_pcnst(ispec, imode)]; + qgcm_cldbrn = + qgcm_cldbrn + qqcwgcm[lmassptr_amode_gas_pcnst(ispec, imode)]; + } + + Real factor_clea; // partitioning factor for clear subarea [unitless] + Real factor_cldy; // partitioning factor for cloudy subarea [unitless] + get_partition_factors(qgcm_intrst, qgcm_cldbrn, fcldy, fclea, // in + factor_clea, factor_cldy); // out + + // apply partitioning factors + // Here jclea==1 and jcldy==2 + for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { + const int icnst = lmassptr_amode_gas_pcnst(ispec, imode); + qsubx[icnst][jclea] = qgcmx[icnst] * factor_clea; + qsubx[icnst][jcldy] = qgcmx[icnst] * factor_cldy; + } // ispec + } // imode + +} // set_subarea_qmass_for_intrst_aerosols + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void set_subarea_gases_and_aerosols( + const int &nsubarea, const int &jclea, // in + const int &jcldy, // in + const Real &fclea, const Real &fcldy, // in + const Real (&qgcm1)[gas_pcnst], const Real (&qgcm2)[gas_pcnst], // in + const Real (&qqcwgcm2)[gas_pcnst], // in + const Real (&qgcm3)[gas_pcnst], // in + const Real (&qqcwgcm3)[gas_pcnst], // in + Real (&qsub1)[gas_pcnst][maxsubarea()], // out + Real (&qsub2)[gas_pcnst][maxsubarea()], // out + Real (&qqcwsub2)[gas_pcnst][maxsubarea()], // out + Real (&qsub3)[gas_pcnst][maxsubarea()], // out + Real (&qqcwsub3)[gas_pcnst][maxsubarea()]) // out +{ + //------------------------------------------------------------------------------------------------ + // Purpose: Partition grid cell mean mixing ratios to clear/cloudy subareas. + //------------------------------------------------------------------------------------------------ + // nsubarea: # of active subareas in the current grid cell + // jclea, jcldy: indices of the clear and cloudy subareas + // fclea, fcldy: area fractions of the clear and cloudy subareas [unitless] + + // The next set of argument variables are tracer mixing ratios. + // - The units are different for gases, aerosol number, and aerosol mass. + // The exact units do not matter for this subroutine, as long as the + // grid cell mean values ("gcm") and the corresponding subarea values + // ("sub") have the same units. + // - qq* and qqcw* are correspond to the interstitial and cloud-borne + // species, respectively + // - The numbers 1-3 correspond to different locations in the host model's + // time integration loop. + + // Grid cell mean mixing ratios + // qgcm1(ncnst), qgcm2(ncnst), qqcwgcm2(ncnst), qgcm3(ncnst), + // qqcwgcm3(ncnst) + + // Subarea mixing ratios + // qsub1(ncnst,maxsubarea), qsub2(ncnst,maxsubarea), + // qsub3(ncnst,maxsubarea), qqcwsub2(ncnst,maxsubarea) + // qqcwsub3(ncnst,maxsubarea) + //---- + + //------------------------------------------------------------------------------------ + // Initialize mixing ratios in subareas before the aerosol microphysics + // calculations + //------------------------------------------------------------------------------------ + // FIXME:Should we set jsub==0 a special value (like NaNs) so that it is never + // used?? + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + for(int jsub = 0; jsub < maxsubarea(); ++jsub) { + // Gases and interstitial aerosols + qsub1[icnst][jsub] = 0; + qsub2[icnst][jsub] = 0; + qsub3[icnst][jsub] = 0; + + // Cloud-borne aerosols + qqcwsub2[icnst][jsub] = 0; + qqcwsub3[icnst][jsub] = 0; } } + //--------------------------------------------------------------------------------------------------- + // Determine which category the current grid cell belongs to: partly cloudy, + // all cloudy, or all clear + //--------------------------------------------------------------------------------------------------- + const bool grid_cell_has_only_clea_area = + ((jclea == 1) && (jcldy == 0) && (nsubarea == 1)); + const bool grid_cell_has_only_cldy_area = + ((jclea == 0) && (jcldy == 1) && (nsubarea == 1)); + const bool gird_cell_is_partly_cldy = + (jclea > 0) && (jcldy > 0) && (jclea + jcldy == 3) && (nsubarea == 2); + + // Sanity check + if((!grid_cell_has_only_clea_area) && (!grid_cell_has_only_cldy_area) && + (!gird_cell_is_partly_cldy)) { + EKAT_KERNEL_ASSERT_MSG(true, + "Error! modal_aero_amicphys - bad jclea, jcldy, " + "nsubarea, jclea, jcldy, nsubarea\n"); + } - // ************************************************************************************************* - // Calculate initial (i.e., before cond/rnam/nnuc/coag) tracer mixing - // ratios within the sub-areas - // - for all-clear or all-cloudy cases, the sub-area TMRs are equal to the - // grid-cell means - // - for partly cloudy case, they are different. This is primarily - // because the - // interstitial aerosol mixing ratios are assumed lower in the cloudy - // sub-area than in the clear sub-area, because much of the aerosol is - // activated in the cloudy sub-area. - // ************************************************************************************************* - // Category I: partly cloudy case - // ************************************************************************************************* - if((jclea > 0) && (jcldy > 0) && (jclea + jcldy == 3) && (nsubarea == 2)) { - // --------------------------------------------------------------------- - // Set GAS mixing ratios in sub-areas (for the condensing gases only!!) - // --------------------------------------------------------------------- - for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if(lmapcc_all(lmz) == lmapcc_val_gas()) { - // assume gas in both sub-areas before gas-chem and cloud-chem equal - // grid-cell mean - for(int i = 0; i < nsubarea; ++i) { - qsub1[lmz][i] = qgcm1[lmz]; - qsub2[lmz][i] = qgcm2[lmz]; - } - // assume gas in clear sub-area after cloud-chem equals before - // cloud-chem value - qsub3[lmz][jclea - 1] = qsub2[lmz][jclea - 1]; - // gas in cloud sub-area then determined by grid-cell mean and clear - // values - qsub3[lmz][jcldy - 1] = - (qgcm3[lmz] - zfclea * qsub3[lmz][jclea - 1]) / zfcldy; - - // check that this does not produce a negative value - if(qsub3[lmz][jcldy - 1] < 0.0) { - qsub3[lmz][jcldy - 1] = 0.0; - qsub3[lmz][jclea - 1] = qgcm3[lmz] / zfclea; - } + //************************************************************************************************* + // Category I: grid cell is either all clear or all cloudy. Copy the grid + // cell mean values. + //************************************************************************************************* + if(grid_cell_has_only_clea_area || grid_cell_has_only_cldy_area) { + // For fully clear and cloudy cells, we populate only 1st index of subarea + // for all output vars + // Makes sense as there is only 1 subarea for these cases. + // FIXME: Should we fill in NaNs for the 0th and 2nd index?? + constexpr int jsub = 1; + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + // copy all gases and aerosols + if(lmapcc_all(icnst) > 0) { + qsub1[icnst][jsub] = qgcm1[icnst]; + qsub2[icnst][jsub] = qgcm2[icnst]; + qsub3[icnst][jsub] = qgcm3[icnst]; + + qqcwsub2[icnst][jsub] = qqcwgcm2[icnst]; + qqcwsub3[icnst][jsub] = qqcwgcm3[icnst]; } } - // --------------------------------------------------------------------- - // Set CLOUD-BORNE AEROSOL mixing ratios in sub-areas. - // This is straightforward, as the same partitioning factors (0 or 1/f) - // are applied to all mass and number mixing ratios in all modes. - // --------------------------------------------------------------------- - // loop thru log-normal modes - for(int n = 0; n < num_modes; ++n) { - // number - then mass of individual species - of a mode - for(int l2 = -1; l2 < num_species_mode(n); ++l2) { - int lc; - if(l2 == -1) - lc = numptr_amode(n); - else - lc = lmassptr_amode(l2, n); - qqcwsub2[lc][jclea - 1] = 0.0; - qqcwsub2[lc][jcldy - 1] = qqcwgcm2[lc] / zfcldy; - qqcwsub3[lc][jclea - 1] = 0.0; - qqcwsub3[lc][jcldy - 1] = qqcwgcm3[lc] / zfcldy; - } + } // if only clear or only cloudy + //************************************************************************************************* + // Category II: partly cloudy grid cell. Tracer mixing ratios are generally + // assumed different in clear and cloudy subareas. This is primarily + // because the interstitial aerosol mixing ratios are assumed to be lower + // in the cloudy sub-area than in the clear sub-area, as much of the + // aerosol is activated in the cloudy sub-area. + //************************************************************************************************* + else if(gird_cell_is_partly_cldy) { + //=================================== + // Set gas mixing ratios in subareas + //=================================== + //------------------------------------------------------------------------------------------ + // Before gas chemistry, gas mixing ratios are assumed to be the same in + // all subareas, i.e., they all equal the grid cell mean. + //------------------------------------------------------------------------------------------ + + // NOTE: In this "else if" case jclea == 1 and jcldy == 2 + + bool cnst_is_gas[gas_pcnst] = {}; + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + cnst_is_gas[icnst] = (lmapcc_all(icnst) == lmapcc_val_gas()); } - // --------------------------------------------------------------------- - // Set INTERSTITIAL AEROSOL mixing ratios in sub-areas. - // --------------------------------------------------------------------- - for(int n = 0; n < num_modes; ++n) { - // ------------------------------------- - // Aerosol number - // ------------------------------------- - // grid cell mean, interstitial - Real tmp_q_cellavg_int = qgcm2[numptr_amode(n)]; - // grid cell mean, cloud-borne - Real tmp_q_cellavg_cbn = qqcwgcm2[numptr_amode(n)]; - - Real nmbr_part_fac_clea = 0; - Real nmbr_part_fac_cldy = 0; - subarea_partition_factors(tmp_q_cellavg_int, tmp_q_cellavg_cbn, zfcldy, - zfclea, nmbr_part_fac_clea, nmbr_part_fac_cldy); - - // Apply the partitioning factors to calculate sub-area mean number - // mixing ratios - - const int la = numptr_amode(n); - - qsub2[la][jclea - 1] = qgcm2[la] * nmbr_part_fac_clea; - qsub2[la][jcldy - 1] = qgcm2[la] * nmbr_part_fac_cldy; - qsub3[la][jclea - 1] = qgcm3[la] * nmbr_part_fac_clea; - qsub3[la][jcldy - 1] = qgcm3[la] * nmbr_part_fac_cldy; - - //------------------------------------- - // Aerosol mass - //------------------------------------- - // For aerosol mass, we use the total grid cell mean - // interstitial/cloud-borne mass mixing ratios to come up with the same - // partitioning for all species in the mode. - - // Compute the total mixing ratios by summing up the individual species - - tmp_q_cellavg_int = 0.0; // grid cell mean, interstitial - tmp_q_cellavg_cbn = 0.0; // grid cell mean, cloud-borne - - for(int l2 = 0; l2 < num_species_mode(n); ++l2) { - tmp_q_cellavg_int += qgcm2[lmassptr_amode(l2, n)]; - tmp_q_cellavg_cbn += qqcwgcm2[lmassptr_amode(l2, n)]; - } - Real mass_part_fac_clea = 0; - Real mass_part_fac_cldy = 0; - // Calculate the partitioning factors - subarea_partition_factors(tmp_q_cellavg_int, tmp_q_cellavg_cbn, zfcldy, - zfclea, mass_part_fac_clea, mass_part_fac_cldy); - - // Apply the partitioning factors to calculate sub-area mean mass mixing - // ratios - - for(int l2 = 0; l2 < num_species_mode(n); ++l2) { - const int la = lmassptr_amode(l2, n); - - qsub2[la][jclea - 1] = qgcm2[la] * mass_part_fac_clea; - qsub2[la][jcldy - 1] = qgcm2[la] * mass_part_fac_cldy; - qsub3[la][jclea - 1] = qgcm3[la] * mass_part_fac_clea; - qsub3[la][jcldy - 1] = qgcm3[la] * mass_part_fac_cldy; + EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), + "Error! set_subarea_gases_and_aerosols: " + "nsubarea should be < maxsubarea() \n"); + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + if(cnst_is_gas[icnst]) { + // For gases, assume both 1 and 2 subareas have grid mean values + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + qsub1[icnst][jsub] = qgcm1[icnst]; + } } } - - // ************************************************************************************************* - // Category II: all clear, or cld < 1e-5 - // In this case, zfclea=1 and zfcldy=0 - // ************************************************************************************************* - } else if((jclea == 1) && (jcldy == 0) && (nsubarea == 1)) { - // - // put all the gases and interstitial aerosols in the clear sub-area - // and set mix-ratios = 0 in cloudy sub-area - // for cloud-borne aerosol, do nothing - // because the grid-cell-mean cloud-borne aerosol will be left - // unchanged (i.e., this routine only changes qqcw when cld >= 1e-5) - // - - for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if(0 < lmapcc_all(lmz)) { - qsub1[lmz][jclea - 1] = qgcm1[lmz]; - qsub2[lmz][jclea - 1] = qgcm2[lmz]; - qsub3[lmz][jclea - 1] = qgcm3[lmz]; - qqcwsub2[lmz][jclea - 1] = qqcwgcm2[lmz]; - qqcwsub3[lmz][jclea - 1] = qqcwgcm3[lmz]; + // qsub1 is fully populated for gasses + //------------------------------------------------------------------------------------------ + // After gas chemistry, still assume gas mixing ratios are the same in all + // subareas. + //------------------------------------------------------------------------------------------ + + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + if(cnst_is_gas[icnst]) { + // For gases, assume both 1 and 2 subareas have grid mean values + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + qsub2[icnst][jsub] = qgcm2[icnst]; + } } } - // ************************************************************************************************* - // Category III: all cloudy, or cld > 0.999 - // in this case, zfcldy= and zfclea=0 - // ************************************************************************************************* - } else if((jclea == 0) && (jcldy == 1) && (nsubarea == 1)) { - // - // put all the gases and interstitial aerosols in the cloudy sub-area - // and set mix-ratios = 0 in clear sub-area - // - for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if(0 < lmapcc_all(lmz)) { - qsub1[lmz][jcldy - 1] = qgcm1[lmz]; - qsub2[lmz][jcldy - 1] = qgcm2[lmz]; - qsub3[lmz][jcldy - 1] = qgcm3[lmz]; - qqcwsub2[lmz][jcldy - 1] = qqcwgcm2[lmz]; - qqcwsub3[lmz][jcldy - 1] = qqcwgcm3[lmz]; + // qsub2 is fully populated for gasses + //---------------------------------------------------------------------------------------- + // After cloud chemistry, gas and aerosol mass mixing ratios in the clear + // subarea are assumed to be the same as their values before cloud + // chemistry (because by definition, cloud chemistry did not happen in + // clear sky), while the mixing ratios in the cloudy subarea likely have + // changed. + //---------------------------------------------------------------------------------------- + // Gases in the clear subarea remain the same as their values before cloud + // chemistry. + // Here we populate qsub3 for index 1 only as jclea is 1. + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + if(cnst_is_gas[icnst]) { + qsub3[icnst][jclea] = qsub2[icnst][jclea]; } } - // ************************************************************************************************* - } else { // this should not happen - EKAT_KERNEL_REQUIRE_MSG( - false, "*** modal_aero_amicphys - bad jclea, jcldy, nsubarea!"); - } - // ************************************************************************************************* - - // ------------------------------------------------------------------------------------ - // aerosol water -- how to treat this in sub-areas needs more work/thinking - // currently modal_aero_water_uptake calculates qaerwat using - // the grid-cell mean interstital-aerosol mix-rats and the clear-area rh - for(int jsub = 0; jsub < nsubarea; ++jsub) - for(int i = 0; i < num_modes; ++i) qaerwatsub3[i][jsub] = qaerwatgcm3[i]; - - // ------------------------------------------------------------------------------------ - if(nsubarea == 1) { - // the j=1 subarea is used for some diagnostics - // but is not used in actual calculations - const int j = 1; - for(int i = 0; i < gas_pcnst(); ++i) { - qsub1[i][j] = 0.0; - qsub2[i][j] = 0.0; - qsub3[i][j] = 0.0; - qqcwsub2[i][j] = 0.0; - qqcwsub3[i][j] = 0.0; + + // Calculate the gas mixing ratios in the cloudy subarea using the + // grid-cell mean, cloud fraction and the clear-sky values + // Here we populate qsub3 for index 2 (jcldy) and adjust index 1 (jclea) if + // needed. + compute_qsub_from_gcm_and_qsub_of_other_subarea(cnst_is_gas, fclea, // in + fcldy, qgcm3, jclea, // in + jcldy, // in + qsub3, qsub3); // inout + // qsub3[2][2]); + // qsub3 is fully populated for gasses + //========================================================================= + // Set AEROSOL mixing ratios in subareas. + // Only need to do this for points 2 and 3 in the time integraion loop, + // i.e., the before-cloud-chem and after-cloud-chem states. + //========================================================================= + // Cloud-borne aerosols. (They are straightforward to partition, + // as they only exist in the cloudy subarea.) + //---------------------------------------------------------------------------------------- + // Partition mass and number before cloud chemistry + // NOTE that in this case jclea is 1 and jcldy is 2 + // Following 2 calls set qqcwsub2(:,1)=0 and qqcwsub2(:,2) to a computed + // value + set_subarea_qnumb_for_cldbrn_aerosols(jclea, jcldy, fcldy, + qqcwgcm2, // in + qqcwsub2); // inout + + set_subarea_qmass_for_cldbrn_aerosols(jclea, jcldy, fcldy, + qqcwgcm2, // in + qqcwsub2); // inout + // Partition mass and number before cloud chemistry + // Following 2 calls set qqcwsub3(:,1)=0 and qqcwsub3(:,2) to a computed + // value + set_subarea_qnumb_for_cldbrn_aerosols(jclea, jcldy, fcldy, + qqcwgcm3, // in + qqcwsub3); // inout + set_subarea_qmass_for_cldbrn_aerosols(jclea, jcldy, fcldy, + qqcwgcm3, // in + qqcwsub3); // inout + + //---------------------------------------------------------------------------------------- + // Interstitial aerosols. (They can exist in both cloudy and clear + // subareas, and hence need to be partitioned.) + //---------------------------------------------------------------------------------------- + // Partition mass and number before cloud chemistry + // Following 2 calls set qsub2(:,1) = 0 and qsub2(:,2) to a computed value + set_subarea_qnumb_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in + qgcm2, qqcwgcm2, qgcm2, // in + qsub2); // inout + + set_subarea_qmass_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in + qgcm2, qqcwgcm2, qgcm2, // in + qsub2); // inout + + // Partition mass and number before cloud chemistry + // Following 2 calls set qsub3(:,1) = 0 and qsub3(:,2) to a computed value + set_subarea_qnumb_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in + qgcm2, qqcwgcm2, qgcm3, // in + qsub3); // inout + + set_subarea_qmass_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in + qgcm2, qqcwgcm2, qgcm3, // in + qsub3); // inout + + } // different categories +} // set_subarea_gases_and_aerosols + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void mam_newnuc_1subarea( + const int igas_h2so4, const int gaexch_h2so4_uptake_optaa, // in + const int newnuc_h2so4_conc_optaa, const int jsub, // in + const Real deltat, const Real temp, const Real pmid, // in + const Real aircon, const Real zmid, const Real pblh, // in + const Real relhum, const Real uptkrate_h2so4, // in + const Real del_h2so4_gasprod, const Real del_h2so4_aeruptk, // in + Real qgas_cur[max_gas()], const Real qgas_avg[max_gas()], // out + Real qnum_cur[AeroConfig::num_modes()], // out + Real qaer_cur[AeroConfig::num_aerosol_ids()] + [AeroConfig::num_modes()], // out + Real dnclusterdt) { // out + // FIXME: This function was not refactored or cleaned in fortran + // we must clean it and remove unused codes and fix variable names + + // nstep: model time-step number + // jsub: sub-area index + // deltat: model timestep (s) + // temp: temperature (K) + // pmid: pressure at model levels(Pa) + // aircon: air molar concentration (kmol/m3) + // zmid: midpoint height above surface (m) + // pblh :pbl height (m) + // relhum:relative humidity (0-1) + // uptkrate_h2so4 + // del_h2so4_gasprod + // del_h2so4_aeruptk + // dnclusterdt: cluster nucleation rate (#/m3/s) + // qgas_cur(max_gas) + // qgas_avg(max_gas) + // qnum_cur(max_mode) + // qaer_cur(1:max_aer,1:max_mode) + // qwtr_cur(1:max_mode) + + // DESCRIPTION: + // computes changes due to aerosol nucleation (new particle formation) + // treats both nucleation and subsequent growth of new particles + // to aitken mode size + // uses the following parameterizations + // vehkamaki et al. (2002) parameterization for binary + // homogeneous nucleation (h2so4-h2o) plus + // kerminen and kulmala (2002) parameterization for + // new particle loss during growth to aitken size + // + // REVISION HISTORY: + // R.Easter 2007.09.14: Adapted from MIRAGE2 code and CMAQ V4.6 code + // + + constexpr int newnuc_method_flagaa = 11; + // 1=merikanto et al (2007) ternary 2=vehkamaki et al (2002) binary + // 11=merikanto ternary + first-order boundary layer + // 12=merikanto ternary + second-order boundary layer + + // begin + dnclusterdt = 0; + + // qh2so4_cur = current qh2so4, after aeruptk + // qh2so4_avg = average qh2so4 over time-step + // BAD CONSTANTS + constexpr Real qh2so4_cutoff = 4.0e-16; + Real qh2so4_cur = qgas_cur[igas_h2so4]; + Real qh2so4_avg, tmp_uptkrate, tmpa; + + // use qh2so4_avg and first-order loss rate calculated in + // mam_gasaerexch_1subarea + qh2so4_avg = qgas_avg[igas_h2so4]; + tmp_uptkrate = uptkrate_h2so4; + + if(qh2so4_avg <= qh2so4_cutoff) return; + + static constexpr int igas_nh3 = -999888777; // Same as mam_refactor + Real qnh3_cur = 0; + + // dry-diameter limits for "grown" new particles + constexpr int nait = static_cast(ModeIndex::Aitken); + Real dplom_mode = haero::exp(0.67 * haero::log(modes(nait).min_diameter) + + 0.33 * haero::log(modes(nait).nom_diameter)); + Real dphim_mode = modes(nait).max_diameter; + + // mass1p_... = mass (kg) of so4 & nh4 in a single particle of diameter ... + // (assuming same dry density for so4 & nh4) + // mass1p_aitlo - dp = dplom_mode + // mass1p_aithi - dp = dphim_mode + constexpr Real dens_so4a_host = 1770; + tmpa = dens_so4a_host * haero::Constants::pi / 6.0; + Real mass1p_aitlo = tmpa * (haero::pow(dplom_mode, 3.0)); + Real mass1p_aithi = tmpa * (haero::pow(dphim_mode, 3.0)); + + // limit RH to between 0.1% and 99% + Real relhumnn = haero::max(0.01, haero::min(0.99, relhum)); + + // BAD CONSTANTS (These should come from chemistry mechanism + // but it is fixed here for stay BFB) + constexpr Real mw_so4a_host = 115; + constexpr Real mwnh4 = 18; + constexpr Real mwso4 = 96; + // BAD CONSTANTS (These should come from Haero constants + // but it is fixed here for stay BFB) + constexpr Real rgas = 8.31446759100000; + constexpr Real avogadro = 6.022140000000000E+023; + + int itmp; + Real qnuma_del, qso4a_del, qnh4a_del, qh2so4_del, qnh3_del, dens_nh4so4a; + + // call ... routine to get nucleation rates + // FIXME: I GOT ALL ZEROS...THIS IS NOT VALIDATED YET!!!! + mam4::nucleation::mer07_veh02_nuc_mosaic_1box( + newnuc_method_flagaa, deltat, temp, relhumnn, pmid, zmid, pblh, // in + qh2so4_cur, qh2so4_avg, qnh3_cur, tmp_uptkrate, mw_so4a_host, 1, // in + dplom_mode, dphim_mode, rgas, avogadro, mwnh4, mwso4, // in + haero::Constants::pi, // in + itmp, qnuma_del, qso4a_del, qnh4a_del, qh2so4_del, // out + qnh3_del, dens_nh4so4a, dnclusterdt); // out + + // convert qnuma_del from (#/mol-air) to (#/kmol-air) + qnuma_del = qnuma_del * 1.0e3; + + // number nuc rate (#/kmol-air/s) from number nuc amt + Real dndt_ait = qnuma_del / deltat; + + // fraction of mass nuc going to so4 + tmpa = qso4a_del * mw_so4a_host; + Real tmpb = tmpa; + Real tmp_frso4 = 1.0; + + // mass nuc rate (kg/kmol-air/s) from mass nuc amts + EKAT_KERNEL_ASSERT_MSG(deltat != 0, + "Error! mam_newnuc_1subarea: " + " deltat should not be equal to 0\n"); + Real dmdt_ait = haero::max(0.0, (tmpb / deltat)); + + Real dndt_aitsv2 = 0.0; + Real dmdt_aitsv2 = 0.0; + Real dndt_aitsv3 = 0.0; + Real dmdt_aitsv3 = 0.0; + // BAD CONSTANTS + if(dndt_ait < 1.0e2) { + // ignore newnuc if number rate < 100 #/kmol-air/s ~= 0.3 #/mg-air/d + dndt_ait = 0.0; + dmdt_ait = 0.0; + } else { + dndt_aitsv2 = dndt_ait; + dmdt_aitsv2 = dmdt_ait; + + // mirage2 code checked for complete h2so4 depletion here, + // but this is now done in mer07_veh02_nuc_mosaic_1box + EKAT_KERNEL_ASSERT_MSG(dndt_ait != 0, + "Error! mam_newnuc_1subarea: " + " dndt_ait should not be equal to 0\n"); + Real mass1p = dmdt_ait / dndt_ait; + dndt_aitsv3 = dndt_ait; + dmdt_aitsv3 = dmdt_ait; + + EKAT_KERNEL_ASSERT_MSG(mass1p_aitlo != 0, + "Error! mam_newnuc_1subarea: " + " mass1p_aitlo should not be equal to 0\n"); + // apply particle size constraints + if(mass1p < mass1p_aitlo) { + // reduce dndt to increase new particle size + dndt_ait = dmdt_ait / mass1p_aitlo; + } else if(mass1p > mass1p_aithi) { + // reduce dmdt to decrease new particle size + dmdt_ait = dndt_ait * mass1p_aithi; } } -} + // *** apply adjustment factor to avoid unrealistically high + // aitken number concentrations in mid and upper troposphere + constexpr Real newnuc_adjust_factor_dnaitdt = 1; + dndt_ait = dndt_ait * newnuc_adjust_factor_dnaitdt; + dmdt_ait = dmdt_ait * newnuc_adjust_factor_dnaitdt; + + Real tmp_q_del = dndt_ait * deltat; + qnum_cur[nait] = qnum_cur[nait] + tmp_q_del; + + // dso4dt_ait, dnh4dt_ait are (kmol/kmol-air/s) + + constexpr Real mw_nh4a_host = mw_so4a_host; + EKAT_KERNEL_ASSERT_MSG(mw_so4a_host != 0, + "Error! mam_newnuc_1subarea: " + " mw_so4a_host should not be equal to 0\n"); + Real dso4dt_ait = dmdt_ait * tmp_frso4 / mw_so4a_host; + EKAT_KERNEL_ASSERT_MSG(mw_nh4a_host != 0, + "Error! mam_newnuc_1subarea: " + " mw_nh4a_host should not be equal to 0\n"); + Real dnh4dt_ait = dmdt_ait * (1.0 - tmp_frso4) / mw_nh4a_host; + constexpr int iaer_so4 = 1; + if(dso4dt_ait > 0.0) { + tmp_q_del = dso4dt_ait * deltat; + qaer_cur[iaer_so4][nait] = qaer_cur[iaer_so4][nait] + tmp_q_del; + tmp_q_del = haero::min(tmp_q_del, qgas_cur[igas_h2so4]); + qgas_cur[igas_h2so4] = qgas_cur[igas_h2so4] - tmp_q_del; + } +} // end mam_newnuc_1subarea +//-------------------------------------------------------------------------------- +// Call aerosol microphysics processes for a single (cloudy or clear) subarea +// +// qgas3, qaer3, qaercw3, qnum3, qnumcw3 are the current incoming TMRs +// qgas_cur, qaer_cur, qaercw_cur, qnum_cur, qnumcw_cur are the updated +// outgoing TMRs +// +// In a clear subarea, calculate +// - gas-aerosol exchange (condensation/evaporation) +// - growth from smaller to larger modes (renaming) due to condensation +// - new particle nucleation +// - coagulation +// - transfer of particles from hydrophobic modes to hydrophilic modes +// (aging) +// due to condensation and coagulation +// +// In a cloudy subarea, +// - when do_cond = false, this routine only calculate changes involving +// growth from smaller to larger modes (renaming) following cloud chemistry +// so gas TMRs are not changed +// - when do_cond = true, this routine also calculates changes involving +// gas-aerosol exchange (condensation/evaporation) +// - transfer of particles from hydrophobic modes to hydrophilic modes +// (aging) +// due to condensation +// Currently, in a cloudy subarea, this routine does not do +// - new particle nucleation - because h2so4 gas conc. should be very low in +// cloudy air +// - coagulation - because cloud-borne aerosol would need to be included +//-------------------------------------------------------------------------------- KOKKOS_INLINE_FUNCTION -void mam_amicphys_1subarea_clear( - const AmicPhysConfig &config, const int nstep, const Real deltat, - const int jsub, const int nsubarea, const bool iscldy_subarea, - const Real afracsub, const Real temp, const Real pmid, const Real pdel, - const Real zmid, const Real pblh, const Real relhum, - Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], - Real wetdens[AeroConfig::num_modes()], - const Real qgas1[AeroConfig::num_gas_ids()], - const Real qgas3[AeroConfig::num_gas_ids()], - Real qgas4[AeroConfig::num_gas_ids()], - Real qgas_delaa[AeroConfig::num_gas_ids()][nqtendaa()], - const Real qnum3[AeroConfig::num_modes()], - Real qnum4[AeroConfig::num_modes()], - Real qnum_delaa[AeroConfig::num_modes()][nqtendaa()], - const Real qaer3[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()], - Real qaer4[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()], - Real qaer_delaa[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()] - [nqtendaa()], - const Real qwtr3[AeroConfig::num_modes()], - Real qwtr4[AeroConfig::num_modes()]) { - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); - static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); - - static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); - // Turn off nh3 for now. This is a future enhancement. - static constexpr int igas_nh3 = -999888777; // Same as mam_refactor - static constexpr int iaer_so4 = static_cast(AeroId::SO4); - static constexpr int iaer_pom = static_cast(AeroId::POM); - - const AeroId gas_to_aer[num_gas_ids] = {AeroId::SOA, AeroId::SO4, - AeroId::None}; - - const bool l_gas_condense_to_mode[num_gas_ids][num_modes] = { - {true, true, true, true}, - {true, true, true, true}, - {false, false, false, false}}; - enum { NA, ANAL, IMPL }; - const int eqn_and_numerics_category[num_gas_ids] = {IMPL, ANAL, ANAL}; - - // air molar density (kmol/m3) - // const Real r_universal = Constants::r_gas; // [mJ/(K mol)] - const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor - const Real aircon = pmid / (1000 * r_universal * temp); - const Real alnsg_aer[num_modes] = {0.58778666490211906, 0.47000362924573563, - 0.58778666490211906, 0.47000362924573563}; - const Real uptk_rate_factor[num_gas_ids] = {0.81, 1.0, 1.0}; - // calculates changes to gas and aerosol sub-area TMRs (tracer mixing ratios) - // qgas3, qaer3, qnum3 are the current incoming TMRs - // qgas4, qaer4, qnum4 are the updated outgoing TMRs +void mam_amicphys_1subarea( + // in + const int newnuc_h2so4_conc_optaa, const int gaexch_h2so4_uptake_optaa, + const bool do_cond_sub, const bool do_rename_sub, const bool do_newnuc_sub, + const bool do_coag_sub, const Real deltat, const int jsubarea, + const bool iscldy_subarea, const Real afracsub, const Real temp, + const Real pmid, const Real pdel, const Real zmid, const Real pblh, + const Real relhumsub, const Real (&dgn_a)[num_modes], + const Real (&dgn_awet)[num_modes], const Real (&wetdens)[num_modes], + const Real (&qgas1)[max_gas()], const Real (&qgas3)[max_gas()], + // inout + Real (&qgas_cur)[max_gas()], Real (&qgas_delaa)[max_gas()][nqtendaa()], + // in + const Real (&qnum3)[num_modes], + // inout + Real (&qnum_cur)[num_modes], Real (&qnum_delaa)[num_modes][nqtendaa()], + // in + const Real (&qaer2)[num_aerosol_ids][num_modes], + const Real (&qaer3)[num_aerosol_ids][num_modes], + // inout + Real (&qaer_cur)[num_aerosol_ids][num_modes], + Real (&qaer_delaa)[num_aerosol_ids][num_modes][nqtendaa()], + // in + const Real (&qwtr3)[num_modes], + // inout + Real (&qwtr_cur)[num_modes], + // in + const Real (&qnumcw3)[num_modes], + // inout + Real (&qnumcw_cur)[num_modes], + Real (&qnumcw_delaa)[num_modes][nqqcwtendaa()], + // in + const Real (&qaercw2)[num_aerosol_ids][num_modes], + const Real (&qaercw3)[num_aerosol_ids][num_modes], + // inout + Real (&qaercw_cur)[num_aerosol_ids][num_modes], + Real (&qaercw_delaa)[num_aerosol_ids][num_modes][nqqcwtendaa()]) + +{ + // do_cond_sub, do_rename_sub: true if the aero. microp. process is + // calculated in this subarea + // do_newnuc_sub, do_coag_sub: true if the aero. microp. process is + // calculated in this subarea + // iscldy_subarea: true if sub-area is cloudy + // kk: level indices + // jsubarea, nsubarea: sub-area index, number of sub-areas + // afracsub: fractional area of subarea [unitless, 0-1] + // deltat: time step [s] + // temp: air temperature at model levels [K] + // pmid: air pressure at layer center [Pa] + // pdel: pressure thickness of layer [Pa] + // zmid: altitude (above ground) at layer center [m] + // pblh: planetary boundary layer depth [m] + // relhum: relative humidity [unitless, 0-1] + // dgn_a (max_mode): dry geo. mean diameter [m] of number distribution + // dgn_awet(max_mode): wet geo. mean diameter [m] of number distribution + // wetdens (max_mode): interstitial aerosol wet density [kg/m3] + + // Subare mixing ratios qXXXN (X=gas,aer,wat,num; N=1:4): // - // this routine calculates changes involving - // gas-aerosol exchange (condensation/evaporation) - // growth from smaller to larger modes (renaming) due to condensation - // new particle nucleation - // coagulation - // transfer of particles from hydrophobic modes to hydrophilic modes - // (aging) - // due to condensation and coagulation + // XXX=gas - gas species [kmol/kmol] + // XXX=aer - aerosol mass species (excluding water) [kmol/kmol] + // XXX=wat - aerosol water [kmol/kmol] + // XXX=num - aerosol number [#/kmol] // - // qXXXN (X=gas,aer,wat,num; N=1:4) are sub-area mixing ratios - // XXX=gas - gas species - // XXX=aer - aerosol mass species (excluding water) - // XXX=wat - aerosol water - // XXX=num - aerosol number // N=1 - before gas-phase chemistry // N=2 - before cloud chemistry // N=3 - current incoming values (before gas-aerosol exchange, newnuc, - // coag) N=4 - updated outgoing values (after gas-aerosol exchange, + // coag) N=_cur - updated outgoing values (after gas-aerosol exchange, // newnuc, coag) // - // qXXX_delaa are TMR changes (not tendencies) - // for different processes, which are used to produce history output - // for a clear sub-area, the processes are condensation/evaporation (and - // associated aging), renaming, coagulation, and nucleation - - Real qgas_cur[num_gas_ids]; - for(int i = 0; i < num_gas_ids; ++i) qgas_cur[i] = qgas3[i]; - Real qaer_cur[num_aerosol_ids][num_modes]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) qaer_cur[i][j] = qaer3[i][j]; - - Real qnum_cur[num_modes]; - for(int j = 0; j < num_modes; ++j) qnum_cur[j] = qnum3[j]; - Real qwtr_cur[num_modes]; - for(int j = 0; j < num_modes; ++j) qwtr_cur[j] = qwtr3[j]; - - // qgas_netprod_otrproc = gas net production rate from other processes - // such as gas-phase chemistry and emissions (mol/mol/s) - // this allows the condensation (gasaerexch) routine to apply production and - // condensation loss - // together, which is more accurate numerically - // NOTE - must be >= zero, as numerical method can fail when it is negative - // NOTE - currently only the values for h2so4 and nh3 should be non-zero - Real qgas_netprod_otrproc[num_gas_ids] = {}; - if(config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { - for(int igas = 0; igas < num_gas_ids; ++igas) { - if(igas == igas_h2so4 || igas == igas_nh3) { - // if config.gaexch_h2so4_uptake_optaa == 2, then - // if qgas increases from pre-gaschem to post-cldchem, - // start from the pre-gaschem mix-ratio and add in the production - // during the integration - // if it decreases, - // start from post-cldchem mix-ratio - // *** currently just do this for h2so4 and nh3 + // qgas1, qgas3 [kmol/kmol] + // qgas_cur [kmol/kmol] + + // qnum3 [#/kmol] + // qnum_cur [#/kmol] + + // qaer2, qaer3 [kmol/kmol] + // qaer_cur[kmol/kmol] + + // qnumcw3[#/kmol] + // qnumcw_cur [#/kmol] + + // qaercw2, qaercw3 [kmol/kmol] + // qaercw_cur [kmol/kmol] + + // qwtr3 [kmol/kmol] + // qwtr_cur [kmol/kmol] + + // qXXX_delaa are TMR changes (increments, not tendencies) of different + // microphysics processes. These are diagnostics sent to history output; + // they do not directly affect time integration. + + // qgas_delaa [kmol/kmol] + // qnum_delaa [ #/kmol] + // qaer_delaa [kmol/kmol] + // qnumcw_delaa [ #/kmol] + // qaercw_delaa [kmol/kmol] + + // type ( misc_vars_aa_type ), intent(inout) :: misc_vars_aa_sub + + //--------------------------------------------------------------------------------------- + // Calculate air molar density [kmol/m3] to be passed on to individual + // parameterizations + //--------------------------------------------------------------------------------------- + // BAD CONSTANT + // Universal gas constant (J/K/kmol) + constexpr Real r_universal = 8314.46759100000; + const Real aircon = pmid / (r_universal * temp); + + //---------------------------------------------------------- + // Initializ mixing ratios with the before-amicphys values + //---------------------------------------------------------- + + copy_1d_array(max_gas(), qgas3, // in + qgas_cur); // out + + constexpr int nspecies = num_aerosol_ids; + constexpr int nmodes = num_modes; + + copy_2d_array(nspecies, nmodes, qaer3, // in + qaer_cur); // out + + copy_1d_array(nmodes, qnum3, // in + qnum_cur); // out + + copy_1d_array(nmodes, qwtr3, // in + qwtr_cur); // out + + if(iscldy_subarea) { + copy_1d_array(nmodes, qnumcw3, // in + qnumcw_cur); // out + copy_2d_array(nspecies, nmodes, qaercw3, // in + qaercw_cur); // out + } // iscldy_subarea + + //--------------------------------------------------------------------- + // Diagnose net production rate of H2SO4 gas production + // cause by other processes (e.g., gas chemistry and cloud chemistry) + //--------------------------------------------------------------------- + Real qgas_netprod_otrproc[max_gas()] = {0}; + assign_1d_array(max_gas(), 0.0, // in + qgas_netprod_otrproc); // out + + // If gaexch_h2so4_uptake_optaa == 2, then + // - if qgas increases from pre-gaschem to post-cldchem, + // start from the pre-gaschem mix-ratio and add in the production during + // the integration + // - if it decreases, start from post-cldchem mix-ratio + + if((do_cond_sub) && (gaexch_h2so4_uptake_optaa == 2)) { + for(int igas = 0; igas < max_gas(); ++igas) { + if((igas == igas_h2so4) || (igas == igas_nh3)) { qgas_netprod_otrproc[igas] = (qgas3[igas] - qgas1[igas]) / deltat; - if(qgas_netprod_otrproc[igas] >= 0.0) - qgas_cur[igas] = qgas1[igas]; - else - qgas_netprod_otrproc[igas] = 0.0; - } - } - } - Real qgas_del_cond[num_gas_ids] = {}; - Real qgas_del_nnuc[num_gas_ids] = {}; - Real qgas_del_cond_only[num_gas_ids] = {}; - Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; - Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_coag_in[num_aerosol_ids][AeroConfig::max_agepair()] = {}; - Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; - Real qnum_del_cond[num_modes] = {}; - Real qnum_del_rnam[num_modes] = {}; - Real qnum_del_nnuc[num_modes] = {}; - Real qnum_del_coag[num_modes] = {}; - Real qnum_delsub_cond[num_modes] = {}; - Real qnum_delsub_coag[num_modes] = {}; - Real qnum_del_cond_only[num_modes] = {}; - Real dnclusterdt = 0.0; - - const int ntsubstep = 1; - Real dtsubstep = deltat; - if(ntsubstep > 1) dtsubstep = deltat / ntsubstep; - Real del_h2so4_gasprod = - haero::max(qgas3[igas_h2so4] - qgas1[igas_h2so4], 0.0) / ntsubstep; + qgas_cur[igas] = (qgas_netprod_otrproc[igas] >= 0) ? qgas1[igas] : 0; + } // h2so4, igas_nh3 + } // igas + } // do_cond_sub,gaexch_h2so4_uptake_optaa - // loop over multiple time sub-steps - for(int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { - // gas-aerosol exchange - Real uptkrate_h2so4 = 0.0; - Real del_h2so4_aeruptk = 0.0; - Real qaer_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; - Real qgas_avg[num_gas_ids] = {}; - Real qnum_sv1[num_modes] = {}; - Real qaer_sv1[num_aerosol_ids][num_modes] = {}; - Real qgas_sv1[num_gas_ids] = {}; - - if(config.do_cond) { - const bool l_calc_gas_uptake_coeff = jtsubstep == 1; - Real uptkaer[num_gas_ids][num_modes] = {}; - - for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; - - // time sub-step - const Real dtsub_soa_fixed = -1.0; - // Integration order - const int nghq = 2; - const int ntot_soamode = 4; - int niter_out = 0; - Real g0_soa_out = 0; - gasaerexch::mam_gasaerexch_1subarea( - nghq, igas_h2so4, igas_nh3, ntot_soamode, gas_to_aer, iaer_so4, - iaer_pom, l_calc_gas_uptake_coeff, l_gas_condense_to_mode, - eqn_and_numerics_category, dtsubstep, dtsub_soa_fixed, temp, pmid, - aircon, num_gas_ids, qgas_cur, qgas_avg, qgas_netprod_otrproc, - qaer_cur, qnum_cur, dgn_awet, alnsg_aer, uptk_rate_factor, uptkaer, - uptkrate_h2so4, niter_out, g0_soa_out); - - if(config.newnuc_h2so4_conc_optaa == 11) - qgas_avg[igas_h2so4] = - 0.5 * (qgas_sv1[igas_h2so4] + qgas_cur[igas_h2so4]); - else if(config.newnuc_h2so4_conc_optaa == 12) - qgas_avg[igas_h2so4] = qgas_cur[igas_h2so4]; - - for(int i = 0; i < num_gas_ids; ++i) - qgas_del_cond[i] += - (qgas_cur[i] - (qgas_sv1[i] + qgas_netprod_otrproc[i] * dtsubstep)); - - for(int i = 0; i < num_modes; ++i) - qnum_delsub_cond[i] = qnum_cur[i] - qnum_sv1[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_delsub_cond[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - - // qaer_del_grow4rnam = change in qaer_del_cond during latest condensation - // calculations - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_delsub_grow4rnam[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - for(int i = 0; i < num_gas_ids; ++i) - qgas_del_cond_only[i] = qgas_del_cond[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_del_cond_only[i][j] = qaer_delsub_cond[i][j]; - for(int i = 0; i < num_modes; ++i) - qnum_del_cond_only[i] = qnum_delsub_cond[i]; - del_h2so4_aeruptk = - qgas_cur[igas_h2so4] - - (qgas_sv1[igas_h2so4] + qgas_netprod_otrproc[igas_h2so4] * dtsubstep); - } else { - for(int i = 0; i < num_gas_ids; ++i) qgas_avg[i] = qgas_cur[i]; - } + constexpr int ntsubstep = 1; + const Real del_h2so4_gasprod = + haero::max(qgas3[igas_h2so4] - qgas1[igas_h2so4], 0) / ntsubstep; + //----------------------------------- + // Initialize increment diagnostics + //----------------------------------- - // renaming after "continuous growth" - if(config.do_rename) { - constexpr int nmodes = AeroConfig::num_modes(); - constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); - const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT - const int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; + assign_2d_array(max_gas(), nqtendaa(), 0, // in + qgas_delaa); // out - Real qnumcw_cur[num_modes] = {}; - Real qaercw_cur[num_aerosol_ids][num_modes] = {}; - Real qaercw_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; - Real mean_std_dev[nmodes]; - Real fmode_dist_tail_fac[nmodes]; - Real v2n_lo_rlx[nmodes]; - Real v2n_hi_rlx[nmodes]; - Real ln_diameter_tail_fac[nmodes]; - int num_pairs = 0; - Real diameter_cutoff[nmodes]; - Real ln_dia_cutoff[nmodes]; - Real diameter_threshold[nmodes]; - Real mass_2_vol[naerosol_species] = {0.15, - 6.4971751412429377e-002, - 0.15, - 7.0588235294117650e-003, - 3.0789473684210526e-002, - 5.1923076923076926e-002, - 156.20986883198000}; - - rename::find_renaming_pairs(dest_mode_of_mode, // in - mean_std_dev, // out - fmode_dist_tail_fac, // out - v2n_lo_rlx, // out - v2n_hi_rlx, // out - ln_diameter_tail_fac, // out - num_pairs, // out - diameter_cutoff, // out - ln_dia_cutoff, diameter_threshold); - - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; - Real dgnum_amode[nmodes]; - for(int m = 0; m < nmodes; ++m) { - dgnum_amode[m] = modes(m).nom_diameter; - } + assign_2d_array(nmodes, nqtendaa(), 0, // in + qnum_delaa); // out - { - Real qmol_i_cur[num_modes][num_aerosol_ids]; - Real qmol_i_del[num_modes][num_aerosol_ids]; - Real qmol_c_cur[num_modes][num_aerosol_ids]; - Real qmol_c_del[num_modes][num_aerosol_ids]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) { - qmol_i_cur[i][j] = qaer_cur[j][i]; - qmol_i_del[i][j] = qaer_delsub_grow4rnam[j][i]; - qmol_c_cur[i][j] = qaercw_cur[j][i]; - qmol_c_del[i][j] = qaercw_delsub_grow4rnam[j][i]; - } - Rename rename; - rename.mam_rename_1subarea_( - iscldy_subarea, smallest_dryvol_value, dest_mode_of_mode, - mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, v2n_hi_rlx, - ln_diameter_tail_fac, num_pairs, diameter_cutoff, ln_dia_cutoff, - diameter_threshold, mass_2_vol, dgnum_amode, qnum_cur, qmol_i_cur, - qmol_i_del, qnumcw_cur, qmol_c_cur, qmol_c_del); - - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) { - qaer_cur[j][i] = qmol_i_cur[i][j]; - qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; - qaercw_cur[j][i] = qmol_c_cur[i][j]; - qaercw_delsub_grow4rnam[j][i] = qmol_c_del[i][j]; - } - } + assign_3d_array(nspecies, nmodes, nqtendaa(), 0, // in + qaer_delaa); // out - for(int i = 0; i < num_modes; ++i) - qnum_del_rnam[i] += qnum_cur[i] - qnum_sv1[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_del_rnam[i][j] += qaer_cur[i][j] - qaer_sv1[i][j]; - } + assign_2d_array(nmodes, nqqcwtendaa(), 0, // in + qnumcw_delaa); // out - // new particle formation (nucleation) - if(config.do_newnuc) { - for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - Real qaer_cur_tmp[num_modes][num_aerosol_ids]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) { - qaer_sv1[j][i] = qaer_cur[j][i]; - qaer_cur_tmp[i][j] = qaer_cur[j][i]; - } - Real dnclusterdt_substep = 0; - Real dndt_ait = 0; - Real dmdt_ait = 0; - Real dso4dt_ait = 0; - Real dnh4dt_ait = 0; - Nucleation nucleation; - Nucleation::Config config; - config.dens_so4a_host = 1770; - config.mw_nh4a_host = 115; - config.mw_so4a_host = 115; - config.accom_coef_h2so4 = 0.65; - AeroConfig aero_config; - nucleation.init(aero_config, config); - nucleation.compute_tendencies_( - dtsubstep, temp, pmid, aircon, zmid, pblh, relhum, uptkrate_h2so4, - del_h2so4_gasprod, del_h2so4_aeruptk, qgas_cur, qgas_avg, qnum_cur, - qaer_cur_tmp, qwtr_cur, dndt_ait, dmdt_ait, dso4dt_ait, dnh4dt_ait, - dnclusterdt_substep); - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_cur[j][i] = qaer_cur_tmp[i][j]; - - //! Apply the tendencies to the prognostics. - const int nait = static_cast(ModeIndex::Aitken); - qnum_cur[nait] += dndt_ait * dtsubstep; - - if(dso4dt_ait > 0.0) { - static constexpr int iaer_so4 = static_cast(AeroId::SO4); - static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); - - Real delta_q = dso4dt_ait * dtsubstep; - qaer_cur[iaer_so4][nait] += delta_q; - delta_q = haero::min(delta_q, qgas_cur[igas_h2so4]); - qgas_cur[igas_h2so4] -= delta_q; - } + assign_3d_array(nspecies, nmodes, nqqcwtendaa(), 0, // in + qaercw_delaa); // out - if(igas_nh3 > 0 && dnh4dt_ait > 0.0) { - static constexpr int iaer_nh4 = - -9999999; // static_cast(AeroId::NH4); + Real ncluster_tend_nnuc_1grid = 0; - Real delta_q = dnh4dt_ait * dtsubstep; - qaer_cur[iaer_nh4][nait] += delta_q; - delta_q = haero::min(delta_q, qgas_cur[igas_nh3]); - qgas_cur[igas_nh3] -= delta_q; + //*********************************** + // loop over multiple time sub-steps + //*********************************** + EKAT_KERNEL_ASSERT_MSG(ntsubstep != 0, + "Error! mam_amicphys_1subarea: " + " ntsubstep should not be equal to 0\n"); + const int dtsubstep = deltat / ntsubstep; + + Real qgas_sv1[max_gas()]; + Real qnum_sv1[nmodes]; + Real qaer_sv1[nspecies][nmodes]; + + Real del_h2so4_aeruptk; // [kmol/kmol] + Real qgas_avg[max_gas()]; // [kmol/kmol] + + // Mixing ratio increments of sub-timesteps used for process coupling + + Real qnum_delsub_cond[nmodes]; // [ #/kmol] + Real qnum_delsub_coag[nmodes]; // [ #/kmol] + Real qaer_delsub_cond[nspecies][nmodes]; // [ #/kmol] + Real qaer_delsub_coag[nspecies][nmodes]; // [kmol/kmol] + Real qaer_delsub_grow4rnam[nspecies][nmodes]; // [kmol/kmol] + Real qaercw_delsub_grow4rnam[nspecies][nmodes]; // [kmol/kmol] + + constexpr int max_agepair = AeroConfig::max_agepair(); + Real qaer_delsub_coag_in[nspecies][max_agepair]; // [kmol/kmol] + + for(int jtsubstep = 0; jtsubstep < ntsubstep; ++jtsubstep) { + //====================== + // Gas-aerosol exchange + //====================== + Real uptkrate_h2so4 = 0; + + if(do_cond_sub) { + copy_1d_array(max_gas(), qgas_cur, // in + qgas_sv1); // out + copy_1d_array(nmodes, qnum_cur, // in + qnum_sv1); // out + + copy_2d_array(nspecies, nmodes, qaer_cur, // in + qaer_sv1); // out + + // max_mode in MAM4 is different from nmodes(max_mode = nmodes+1) + // Here we create temporary arrays for now, but we should make + // it consistent to avoid this extra memoery + constexpr int max_mode = nmodes + 1; + Real qaer_cur_tmp[nspecies][max_mode]; + Real qnum_cur_tmp[max_mode]; + Real qwtr_cur_tmp[max_mode]; + + // NOTE: we cannot use copy_2d_array here as arrays extent is max_mode + // but we are copying till nmodes + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_cur_tmp[is][im] = qaer_cur[is][im]; + } } - for(int i = 0; i < num_gas_ids; ++i) - qgas_del_nnuc[i] += (qgas_cur[i] - qgas_sv1[i]); - for(int i = 0; i < num_modes; ++i) - qnum_del_nnuc[i] += (qnum_cur[i] - qnum_sv1[i]); - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) - qaer_del_nnuc[j][i] += (qaer_cur[j][i] - qaer_sv1[j][i]); - - dnclusterdt = dnclusterdt + dnclusterdt_substep * (dtsubstep / deltat); - } - // coagulation part - if(config.do_coag) { - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; - coagulation::mam_coag_1subarea(dtsubstep, temp, pmid, aircon, dgn_a, - dgn_awet, wetdens, qnum_cur, qaer_cur, - qaer_delsub_coag_in); - for(int i = 0; i < num_modes; ++i) - qnum_delsub_coag[i] = qnum_cur[i] - qnum_sv1[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) - qaer_delsub_coag[j][i] = qaer_cur[j][i] - qaer_sv1[j][i]; - } + copy_1d_array(nmodes, qwtr_cur, // in + qwtr_cur_tmp); // out - // primary carbon aging + copy_1d_array(nmodes, qnum_cur, // in + qnum_cur_tmp); // out - aging::mam_pcarbon_aging_1subarea( - dgn_a, qnum_cur, qnum_delsub_cond, qnum_delsub_coag, qaer_cur, - qaer_delsub_cond, qaer_delsub_coag, qaer_delsub_coag_in); - - // accumulate sub-step q-dels - if(config.do_coag) { - for(int i = 0; i < num_modes; ++i) - qnum_del_coag[i] += qnum_delsub_coag[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) - qaer_del_coag[j][i] += qaer_delsub_coag[j][i]; - } - if(config.do_cond) { - for(int i = 0; i < num_modes; ++i) - qnum_del_cond[i] += qnum_delsub_cond[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) - qaer_del_cond[j][i] += qaer_delsub_cond[j][i]; - } - } + Real uptkaer[max_gas()][max_mode]; - // final mix ratios - for(int i = 0; i < num_gas_ids; ++i) qgas4[i] = qgas_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer4[j][i] = qaer_cur[j][i]; - for(int i = 0; i < num_modes; ++i) qnum4[i] = qnum_cur[i]; - for(int i = 0; i < num_modes; ++i) qwtr4[i] = qwtr_cur[i]; + mam4::mam_gasaerexch_1subarea( + jtsubstep, dtsubstep, temp, pmid, aircon, nmodes, // in + qgas_cur, qgas_avg, // inout + qgas_netprod_otrproc, // in + qaer_cur_tmp, qnum_cur_tmp, qwtr_cur_tmp, // inout + dgn_awet, // in + uptkaer, uptkrate_h2so4); // inout - // final mix ratio changes - for(int i = 0; i < num_gas_ids; ++i) { - qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; - qgas_delaa[i][iqtend_rnam()] = 0.0; - qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; - qgas_delaa[i][iqtend_coag()] = 0.0; - qgas_delaa[i][iqtend_cond_only()] = qgas_del_cond_only[i]; - } - for(int i = 0; i < num_modes; ++i) { - qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; - qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; - qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; - qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; - qnum_delaa[i][iqtend_cond_only()] = qnum_del_cond_only[i]; - } - for(int j = 0; j < num_aerosol_ids; ++j) { - for(int i = 0; i < num_modes; ++i) { - qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; - qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; - qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; - qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; - qaer_delaa[j][i][iqtend_cond_only()] = qaer_del_cond_only[j][i]; - } - } -} + // copy back the values for aerosols + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_cur[is][im] = qaer_cur_tmp[is][im]; + } + } -KOKKOS_INLINE_FUNCTION -void mam_amicphys_1subarea_cloudy( - const AmicPhysConfig &config, const int nstep, const Real deltat, - const int jsub, const int nsubarea, const bool iscldy_subarea, - const Real afracsub, const Real temp, const Real pmid, const Real pdel, - const Real zmid, const Real pblh, const Real relhum, - Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], - Real wetdens[AeroConfig::num_modes()], - const Real qgas1[AeroConfig::num_gas_ids()], - const Real qgas3[AeroConfig::num_gas_ids()], - Real qgas4[AeroConfig::num_gas_ids()], - Real qgas_delaa[AeroConfig::num_gas_ids()][nqtendaa()], - const Real qnum3[AeroConfig::num_modes()], - Real qnum4[AeroConfig::num_modes()], - Real qnum_delaa[AeroConfig::num_modes()][nqtendaa()], - const Real qaer2[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()], - const Real qaer3[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()], - Real qaer4[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()], - Real qaer_delaa[AeroConfig::num_aerosol_ids()][AeroConfig::num_modes()] - [nqtendaa()], - const Real qwtr3[AeroConfig::num_modes()], - Real qwtr4[AeroConfig::num_modes()], - const Real qnumcw3[AeroConfig::num_modes()], - Real qnumcw4[AeroConfig::num_modes()], - Real qnumcw_delaa[AeroConfig::num_modes()][nqqcwtendaa()], - const Real qaercw2[AeroConfig::num_gas_ids()][AeroConfig::num_modes()], - const Real qaercw3[AeroConfig::num_gas_ids()][AeroConfig::num_modes()], - Real qaercw4[AeroConfig::num_gas_ids()][AeroConfig::num_modes()], - Real qaercw_delaa[AeroConfig::num_gas_ids()][AeroConfig::num_modes()] - [nqqcwtendaa()]) { - // - // calculates changes to gas and aerosol sub-area TMRs (tracer mixing ratios) - // qgas3, qaer3, qaercw3, qnum3, qnumcw3 are the current incoming TMRs - // qgas4, qaer4, qaercw4, qnum4, qnumcw4 are the updated outgoing TMRs - // - // when config.do_cond = false, this routine only calculates changes involving - // growth from smaller to larger modes (renaming) following cloud chemistry - // so gas TMRs are not changed - // when config.do_cond = true, this routine also calculates changes involving - // gas-aerosol exchange (condensation/evaporation) - // transfer of particles from hydrophobic modes to hydrophilic modes - // (aging) - // due to condensation - // currently this routine does not do - // new particle nucleation - because h2so4 gas conc. should be very low in - // cloudy air coagulation - because cloud-borne aerosol would need to be - // included - // + copy_1d_array(nmodes, qwtr_cur_tmp, // in + qwtr_cur); // out - // qXXXN (X=gas,aer,wat,num; N=1:4) are sub-area mixing ratios - // XXX=gas - gas species - // XXX=aer - aerosol mass species (excluding water) - // XXX=wat - aerosol water - // XXX=num - aerosol number - // N=1 - before gas-phase chemistry - // N=2 - before cloud chemistry - // N=3 - current incoming values (before gas-aerosol exchange, newnuc, - // coag) N=4 - updated outgoing values (after gas-aerosol exchange, - // newnuc, coag) - // - // qXXX_delaa are TMR changes (not tendencies) - // for different processes, which are used to produce history output - // for a clear sub-area, the processes are condensation/evaporation (and - // associated aging), - // renaming, coagulation, and nucleation + copy_1d_array(nmodes, qnum_cur_tmp, // in + qnum_cur); // out - // qxxx_del_yyyy are mix-ratio changes over full time step (deltat) - // qxxx_delsub_yyyy are mix-ratio changes over time sub-step (dtsubstep) + for(int ig = 0; ig < max_gas(); ++ig) { + qgas_delaa[ig][iqtend_cond()] = + qgas_delaa[ig][iqtend_cond()] + + (qgas_cur[ig] - + (qgas_sv1[ig] + qgas_netprod_otrproc[ig] * dtsubstep)); + } + for(int im = 0; im < nmodes; ++im) { + qnum_delsub_cond[im] = qnum_cur[im] - qnum_sv1[im]; + } + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delsub_cond[is][im] = qaer_cur[is][im] - qaer_sv1[is][im]; + } + } - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); - static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); + del_h2so4_aeruptk = + qgas_cur[igas_h2so4] - + (qgas_sv1[igas_h2so4] + qgas_netprod_otrproc[igas_h2so4] * dtsubstep); - static constexpr int igas_h2so4 = static_cast(GasId::H2SO4); - // Turn off nh3 for now. This is a future enhancement. - static constexpr int igas_nh3 = -999888777; // Same as mam_refactor - static constexpr int iaer_so4 = static_cast(AeroId::SO4); - static constexpr int iaer_pom = static_cast(AeroId::POM); - - const AeroId gas_to_aer[num_gas_ids] = {AeroId::SOA, AeroId::SO4, - AeroId::None}; - const bool l_gas_condense_to_mode[num_gas_ids][num_modes] = { - {true, true, true, true}, - {true, true, true, true}, - {false, false, false, false}}; - enum { NA, ANAL, IMPL }; - const int eqn_and_numerics_category[num_gas_ids] = {IMPL, ANAL, ANAL}; - // air molar density (kmol/m3) - // In order to try to match the results in mam_refactor - // set r_universal as [mJ/(mol)] as in mam_refactor. - // const Real r_universal = Constants::r_gas; // [mJ/(K mol)] - const Real r_universal = 8.314467591; // [mJ/(mol)] as in mam_refactor - const Real aircon = pmid / (1000 * r_universal * temp); - const Real alnsg_aer[num_modes] = {0.58778666490211906, 0.47000362924573563, - 0.58778666490211906, 0.47000362924573563}; - const Real uptk_rate_factor[num_gas_ids] = {0.81, 1.0, 1.0}; - - Real qgas_cur[num_gas_ids]; - for(int i = 0; i < num_gas_ids; ++i) qgas_cur[i] = qgas3[i]; - Real qaer_cur[num_aerosol_ids][num_modes]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) qaer_cur[i][j] = qaer3[i][j]; - - Real qnum_cur[num_modes]; - for(int j = 0; j < num_modes; ++j) qnum_cur[j] = qnum3[j]; - Real qwtr_cur[num_modes]; - for(int j = 0; j < num_modes; ++j) qwtr_cur[j] = qwtr3[j]; - - Real qnumcw_cur[num_modes]; - for(int j = 0; j < num_modes; ++j) qnumcw_cur[j] = qnumcw3[j]; - - Real qaercw_cur[num_gas_ids][num_modes]; - for(int i = 0; i < num_gas_ids; ++i) - for(int j = 0; j < num_modes; ++j) qaercw_cur[i][j] = qaercw3[i][j]; - - Real qgas_netprod_otrproc[num_gas_ids] = {}; - if(config.do_cond && config.gaexch_h2so4_uptake_optaa == 2) { - for(int igas = 0; igas < num_gas_ids; ++igas) { - if(igas == igas_h2so4 || igas == igas_nh3) { - // if gaexch_h2so4_uptake_optaa == 2, then - // if qgas increases from pre-gaschem to post-cldchem, - // start from the pre-gaschem mix-ratio and add in the production - // during the integration - // if it decreases, - // start from post-cldchem mix-ratio - // *** currently just do this for h2so4 and nh3 - qgas_netprod_otrproc[igas] = (qgas3[igas] - qgas1[igas]) / deltat; - if(qgas_netprod_otrproc[igas] >= 0.0) - qgas_cur[igas] = qgas1[igas]; - else - qgas_netprod_otrproc[igas] = 0.0; + } else { // do_cond_sub + copy_1d_array(max_gas(), qgas_cur, // in + qgas_avg); // out + + assign_2d_array(nspecies, nmodes, 0, // in + qaer_delsub_cond); // out + + assign_1d_array(nmodes, 0.0, // in + qnum_delsub_cond); // out + del_h2so4_aeruptk = 0; + + } // do_cond_sub + + //==================================== + // Renaming after "continuous growth" + //==================================== + if(do_rename_sub) { + constexpr int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; + + //--------------------------------------------------------- + // Calculate changes in aerosol mass mixing ratios due to + // - gas condensation/evaporation + // - cloud chemistry (if the subarea is cloudy) + //--------------------------------------------------------- + copy_2d_array(nspecies, nmodes, qaer_delsub_cond, // in + qaer_delsub_grow4rnam); // out + + if(iscldy_subarea) { + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delsub_grow4rnam[is][im] = + (qaer3[is][im] - qaer2[is][im]) / ntsubstep + + qaer_delsub_grow4rnam[is][im]; + qaercw_delsub_grow4rnam[is][im] = + (qaercw3[is][im] - qaercw2[is][im]) / ntsubstep; + } + } } - } - } - Real qgas_del_cond[num_gas_ids] = {}; - Real qgas_del_nnuc[num_gas_ids] = {}; - Real qgas_del_cond_only[num_gas_ids] = {}; - Real qaer_del_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qaer_del_nnuc[num_aerosol_ids][num_modes] = {}; - Real qaer_del_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_cond[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_coag[num_aerosol_ids][num_modes] = {}; - Real qaer_del_cond_only[num_aerosol_ids][num_modes] = {}; - Real qaercw_del_rnam[num_aerosol_ids][num_modes] = {}; - Real qnum_del_cond[num_modes] = {}; - Real qnum_del_rnam[num_modes] = {}; - Real qnum_del_nnuc[num_modes] = {}; - Real qnum_del_coag[num_modes] = {}; - Real qnum_delsub_cond[num_modes] = {}; - Real qnum_delsub_coag[num_modes] = {}; - Real qnum_del_cond_only[num_modes] = {}; - Real qnumcw_del_rnam[num_modes] = {}; - Real qaer_delsub_coag_in[num_aerosol_ids][AeroConfig::max_agepair()] = {}; - - const int ntsubstep = 1; - Real dtsubstep = deltat; - if(ntsubstep > 1) dtsubstep = deltat / ntsubstep; - // loop over multiple time sub-steps + //---------- + // Renaming + //---------- + copy_1d_array(nmodes, qnum_cur, // in + qnum_sv1); // out - for(int jtsubstep = 1; jtsubstep <= ntsubstep; ++jtsubstep) { - // gas-aerosol exchange - Real uptkrate_h2so4 = 0.0; - Real qgas_avg[num_gas_ids] = {}; - Real qgas_sv1[num_gas_ids] = {}; - Real qnum_sv1[num_modes] = {}; - Real qaer_sv1[num_aerosol_ids][num_modes] = {}; - Real qaer_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; - - if(config.do_cond) { - const bool l_calc_gas_uptake_coeff = jtsubstep == 1; - Real uptkaer[num_gas_ids][num_modes] = {}; - - for(int i = 0; i < num_gas_ids; ++i) qgas_sv1[i] = qgas_cur[i]; - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; - - const int nghq = 2; - const int ntot_soamode = 4; - int niter_out = 0; - Real g0_soa_out = 0; - // time sub-step - const Real dtsub_soa_fixed = -1.0; - gasaerexch::mam_gasaerexch_1subarea( - nghq, igas_h2so4, igas_nh3, ntot_soamode, gas_to_aer, iaer_so4, - iaer_pom, l_calc_gas_uptake_coeff, l_gas_condense_to_mode, - eqn_and_numerics_category, dtsubstep, dtsub_soa_fixed, temp, pmid, - aircon, num_gas_ids, qgas_cur, qgas_avg, qgas_netprod_otrproc, - qaer_cur, qnum_cur, dgn_awet, alnsg_aer, uptk_rate_factor, uptkaer, - uptkrate_h2so4, niter_out, g0_soa_out); - - if(config.newnuc_h2so4_conc_optaa == 11) - qgas_avg[igas_h2so4] = - 0.5 * (qgas_sv1[igas_h2so4] + qgas_cur[igas_h2so4]); - else if(config.newnuc_h2so4_conc_optaa == 12) - qgas_avg[igas_h2so4] = qgas_cur[igas_h2so4]; - - for(int i = 0; i < num_gas_ids; ++i) - qgas_del_cond[i] += - (qgas_cur[i] - (qgas_sv1[i] + qgas_netprod_otrproc[i] * dtsubstep)); - - for(int i = 0; i < num_modes; ++i) - qnum_delsub_cond[i] = qnum_cur[i] - qnum_sv1[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_delsub_cond[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - - // qaer_del_grow4rnam = change in qaer_del_cond during latest condensation - // calculations - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_delsub_grow4rnam[i][j] = qaer_cur[i][j] - qaer_sv1[i][j]; - for(int i = 0; i < num_gas_ids; ++i) - qgas_del_cond_only[i] = qgas_del_cond[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_del_cond_only[i][j] = qaer_delsub_cond[i][j]; - for(int i = 0; i < num_modes; ++i) - qnum_del_cond_only[i] = qnum_delsub_cond[i]; + copy_2d_array(nspecies, nmodes, qaer_cur, // in + qaer_sv1); // out + + Real qnumcw_sv1[nmodes]; + copy_1d_array(nmodes, qnumcw_cur, // in + qnumcw_sv1); // out + Real qaercw_sv1[nspecies][nmodes]; + copy_2d_array(nspecies, nmodes, qaercw_cur, // in + qaercw_sv1); // out - } else { - for(int i = 0; i < num_gas_ids; ++i) qgas_avg[i] = qgas_cur[i]; - } - // renaming after "continuous growth" - if(config.do_rename) { - constexpr int nmodes = AeroConfig::num_modes(); - constexpr int naerosol_species = AeroConfig::num_aerosol_ids(); - const Real smallest_dryvol_value = 1.0e-25; // BAD_CONSTANT - const int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; - - Real qnumcw_cur[num_modes] = {}; - Real qaercw_cur[num_aerosol_ids][num_modes] = {}; - Real qaercw_delsub_grow4rnam[num_aerosol_ids][num_modes] = {}; Real mean_std_dev[nmodes]; Real fmode_dist_tail_fac[nmodes]; Real v2n_lo_rlx[nmodes]; @@ -1168,165 +1489,237 @@ void mam_amicphys_1subarea_cloudy( Real diameter_cutoff[nmodes]; Real ln_dia_cutoff[nmodes]; Real diameter_threshold[nmodes]; - Real mass_2_vol[naerosol_species] = {0.15, - 6.4971751412429377e-002, - 0.15, - 7.0588235294117650e-003, - 3.0789473684210526e-002, - 5.1923076923076926e-002, - 156.20986883198000}; - - rename::find_renaming_pairs(dest_mode_of_mode, // in - mean_std_dev, // out - fmode_dist_tail_fac, // out - v2n_lo_rlx, // out - v2n_hi_rlx, // out - ln_diameter_tail_fac, // out - num_pairs, // out - diameter_cutoff, // out - ln_dia_cutoff, diameter_threshold); - - for(int i = 0; i < num_modes; ++i) qnum_sv1[i] = qnum_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer_sv1[j][i] = qaer_cur[j][i]; + + rename::find_renaming_pairs( + dest_mode_of_mode, // in + mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, // out + v2n_hi_rlx, ln_diameter_tail_fac, num_pairs, // out + diameter_cutoff, ln_dia_cutoff, // out + diameter_threshold); // out Real dgnum_amode[nmodes]; for(int m = 0; m < nmodes; ++m) { dgnum_amode[m] = modes(m).nom_diameter; } + // BAD_CONSTANT + constexpr Real smallest_dryvol_value = 1.0e-25; + + // swap dimensions as mam_rename_1subarea_ uses output arrays in + // a swapped dimension order + Real qaer_cur_tmp[nmodes][nspecies]; + Real qaer_delsub_grow4rnam_tmp[nmodes][nspecies]; + Real qaercw_cur_tmp[nmodes][nspecies]; + Real qaercw_delsub_grow4rnam_tmp[nmodes][nspecies]; + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_cur_tmp[im][is] = qaer_cur[is][im]; + qaer_delsub_grow4rnam_tmp[im][is] = qaer_delsub_grow4rnam[is][im]; + qaercw_cur_tmp[im][is] = qaercw_cur[is][im]; + qaercw_delsub_grow4rnam_tmp[im][is] = qaercw_delsub_grow4rnam[is][im]; + } + } + Rename rename; + rename.mam_rename_1subarea_( + iscldy_subarea, smallest_dryvol_value, dest_mode_of_mode, // in + mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, v2n_hi_rlx, // in + ln_diameter_tail_fac, num_pairs, diameter_cutoff, // in + ln_dia_cutoff, diameter_threshold, mass_2_vol, dgnum_amode, // in + qnum_cur, qaer_cur_tmp, // out + qaer_delsub_grow4rnam_tmp, // in + qnumcw_cur, qaercw_cur_tmp, // out + qaercw_delsub_grow4rnam_tmp); // in + + // copy the output back to the variables + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_cur[is][im] = qaer_cur_tmp[im][is]; + qaer_delsub_grow4rnam[is][im] = qaer_delsub_grow4rnam_tmp[im][is]; + qaercw_cur[is][im] = qaercw_cur_tmp[im][is]; + qaercw_delsub_grow4rnam[is][im] = qaercw_delsub_grow4rnam_tmp[im][is]; + } + } - // qaercw_delsub_grow4rnam = change in qaercw from cloud chemistry - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaercw_delsub_grow4rnam[i][j] = - (qaercw3[i][j] - qaercw2[i][j]) / ntsubstep; - Real qnumcw_sv1[num_modes]; - for(int i = 0; i < num_modes; ++i) qnumcw_sv1[i] = qnumcw_cur[i]; - Real qaercw_sv1[num_aerosol_ids][num_modes]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) qaercw_sv1[i][j] = qaercw_cur[i][j]; - - { - Real qmol_i_cur[num_modes][num_aerosol_ids]; - Real qmol_i_del[num_modes][num_aerosol_ids]; - Real qmol_c_cur[num_modes][num_aerosol_ids]; - Real qmol_c_del[num_modes][num_aerosol_ids]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) { - qmol_i_cur[i][j] = qaer_cur[j][i]; - qmol_i_del[i][j] = qaer_delsub_grow4rnam[j][i]; - qmol_c_cur[i][j] = qaercw_cur[j][i]; - qmol_c_del[i][j] = qaercw_delsub_grow4rnam[j][i]; - } + //------------------------ + // Accumulate increments + //------------------------ + for(int im = 0; im < nmodes; ++im) { + qnum_delaa[im][iqtend_rnam()] = + qnum_delaa[im][iqtend_rnam()] + (qnum_cur[im] - qnum_sv1[im]); + } - Rename rename; - rename.mam_rename_1subarea_( - iscldy_subarea, smallest_dryvol_value, dest_mode_of_mode, - mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, v2n_hi_rlx, - ln_diameter_tail_fac, num_pairs, diameter_cutoff, ln_dia_cutoff, - diameter_threshold, mass_2_vol, dgnum_amode, qnum_cur, qmol_i_cur, - qmol_i_del, qnumcw_cur, qmol_c_cur, qmol_c_del); - - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) { - qaer_cur[j][i] = qmol_i_cur[i][j]; - qaer_delsub_grow4rnam[j][i] = qmol_i_del[i][j]; - qaercw_cur[j][i] = qmol_c_cur[i][j]; - qaercw_delsub_grow4rnam[j][i] = qmol_c_del[i][j]; - } + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delaa[is][im][iqtend_rnam()] = + qaer_delaa[is][im][iqtend_rnam()] + + (qaer_cur[is][im] - qaer_sv1[is][im]); + } } - for(int i = 0; i < num_modes; ++i) - qnum_del_rnam[i] += qnum_cur[i] - qnum_sv1[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaer_del_rnam[i][j] += qaer_cur[i][j] - qaer_sv1[i][j]; - for(int i = 0; i < num_modes; ++i) - qnumcw_del_rnam[i] += qnumcw_cur[i] - qnumcw_sv1[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaercw_del_rnam[i][j] += qaercw_cur[i][j] - qaercw_sv1[i][j]; - } + if(iscldy_subarea) { + for(int im = 0; im < nmodes; ++im) { + qnumcw_delaa[im][iqqcwtend_rnam()] = + qnumcw_delaa[im][iqqcwtend_rnam()] + + (qnumcw_cur[im] - qnumcw_sv1[im]); + } + } // if iscldy_subarea + + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaercw_delaa[is][im][iqqcwtend_rnam()] = + qaercw_delaa[is][im][iqqcwtend_rnam()] + + (qaercw_cur[is][im] - qaercw_sv1[is][im]); + } + } + } // do_rename_sub + + //==================================== + // New particle formation (nucleation) + //==================================== + if(do_newnuc_sub) { + copy_1d_array(max_gas(), qgas_cur, // in + qgas_sv1); // out + copy_1d_array(nmodes, qnum_cur, // in + qnum_sv1); // out + + copy_2d_array(nspecies, nmodes, qaer_cur, // in + qaer_sv1); // out + + Real dnclusterdt_substep; + mam_newnuc_1subarea(igas_h2so4, gaexch_h2so4_uptake_optaa, + newnuc_h2so4_conc_optaa, jsubarea, dtsubstep, // in + temp, // in + pmid, aircon, zmid, pblh, // in + relhumsub, uptkrate_h2so4, del_h2so4_gasprod, // in + del_h2so4_aeruptk, // in + qgas_cur, qgas_avg, qnum_cur, qaer_cur, // out + dnclusterdt_substep); // out + + for(int ig = 0; ig < max_gas(); ++ig) { + qgas_delaa[ig][iqtend_nnuc()] += (qgas_cur[ig] - qgas_sv1[ig]); + } + for(int im = 0; im < nmodes; ++im) { + qnum_delaa[im][iqtend_nnuc()] += (qnum_cur[im] - qnum_sv1[im]); + } + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delaa[is][im][iqtend_nnuc()] += + (qaer_cur[is][im] - qaer_sv1[is][im]); + } + } + EKAT_KERNEL_ASSERT_MSG(deltat != 0, + "Error! mam_amicphys_1subarea: " + "deltat should not be equal to zero \n"); + ncluster_tend_nnuc_1grid += dnclusterdt_substep * (dtsubstep / deltat); + + } // do_newnuc_sub + + //==================================== + // Coagulation + //==================================== + if(do_coag_sub) { + copy_1d_array(nmodes, qnum_cur, // in + qnum_sv1); // out + + copy_2d_array(nspecies, nmodes, qaer_cur, // in + qaer_sv1); // out + + mam4::coagulation::mam_coag_1subarea( + dtsubstep, temp, pmid, aircon, // in + dgn_awet, wetdens, // in + qnum_cur, qaer_cur, qaer_delsub_coag_in); // inout, inout, out + + for(int im = 0; im < nmodes; ++im) { + qnum_delsub_coag[im] = qnum_cur[im] - qnum_sv1[im]; + } + + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delsub_coag[is][im] = qaer_cur[is][im] - qaer_sv1[is][im]; + } + } + + for(int im = 0; im < nmodes; ++im) { + qnum_delaa[im][iqtend_coag()] += qnum_delsub_coag[im]; + } + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delaa[is][im][iqtend_coag()] += qaer_delsub_coag[is][im]; + } + } + + } else { + assign_2d_array(nspecies, max_agepair, 0.0, // in + qaer_delsub_coag_in); // out + assign_2d_array(nspecies, nmodes, 0.0, // in + qaer_delsub_coag); // out + assign_1d_array(nmodes, 0.0, // in + qnum_delsub_coag); // out + + } // do_coag_sub + + //==================================== // primary carbon aging - if(config.do_cond) { - aging::mam_pcarbon_aging_1subarea( - dgn_a, qnum_cur, qnum_delsub_cond, qnum_delsub_coag, qaer_cur, - qaer_delsub_cond, qaer_delsub_coag, qaer_delsub_coag_in); - } - // accumulate sub-step q-dels - if(config.do_cond) { - for(int i = 0; i < num_modes; ++i) - qnum_del_cond[i] += qnum_delsub_cond[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) - qaer_del_cond[j][i] += qaer_delsub_cond[j][i]; - } - } - // final mix ratios - for(int i = 0; i < num_gas_ids; ++i) qgas4[i] = qgas_cur[i]; - for(int j = 0; j < num_aerosol_ids; ++j) - for(int i = 0; i < num_modes; ++i) qaer4[j][i] = qaer_cur[j][i]; - for(int i = 0; i < num_modes; ++i) qnum4[i] = qnum_cur[i]; - for(int i = 0; i < num_modes; ++i) qwtr4[i] = qwtr_cur[i]; - for(int i = 0; i < num_modes; ++i) qnumcw4[i] = qnumcw_cur[i]; - for(int i = 0; i < num_gas_ids; ++i) - for(int j = 0; j < num_modes; ++j) qaercw4[i][j] = qaercw_cur[i][j]; - - // final mix ratio changes - for(int i = 0; i < num_gas_ids; ++i) { - qgas_delaa[i][iqtend_cond()] = qgas_del_cond[i]; - qgas_delaa[i][iqtend_rnam()] = 0.0; - qgas_delaa[i][iqtend_nnuc()] = qgas_del_nnuc[i]; - qgas_delaa[i][iqtend_coag()] = 0.0; - qgas_delaa[i][iqtend_cond_only()] = qgas_del_cond_only[i]; - } - for(int i = 0; i < num_modes; ++i) { - qnum_delaa[i][iqtend_cond()] = qnum_del_cond[i]; - qnum_delaa[i][iqtend_rnam()] = qnum_del_rnam[i]; - qnum_delaa[i][iqtend_nnuc()] = qnum_del_nnuc[i]; - qnum_delaa[i][iqtend_coag()] = qnum_del_coag[i]; - qnum_delaa[i][iqtend_cond_only()] = qnum_del_cond_only[i]; - } - for(int j = 0; j < num_aerosol_ids; ++j) { - for(int i = 0; i < num_modes; ++i) { - qaer_delaa[j][i][iqtend_cond()] = qaer_del_cond[j][i]; - qaer_delaa[j][i][iqtend_rnam()] = qaer_del_rnam[j][i]; - qaer_delaa[j][i][iqtend_nnuc()] = qaer_del_nnuc[j][i]; - qaer_delaa[j][i][iqtend_coag()] = qaer_del_coag[j][i]; - qaer_delaa[j][i][iqtend_cond_only()] = qaer_del_cond_only[j][i]; - } - } - for(int i = 0; i < num_modes; ++i) - qnumcw_delaa[i][iqqcwtend_rnam()] = qnumcw_del_rnam[i]; - for(int i = 0; i < num_aerosol_ids; ++i) - for(int j = 0; j < num_modes; ++j) - qaercw_delaa[i][j][iqqcwtend_rnam()] = qaercw_del_rnam[i][j]; -} + //==================================== + const bool do_aging_in_subarea = + (n_agepair() > 0) && + ((!iscldy_subarea) || (iscldy_subarea && do_cond_sub)); + + if(do_aging_in_subarea) { + mam4::aging::mam_pcarbon_aging_1subarea( + dgn_a, // input + qnum_cur, qnum_delsub_cond, qnum_delsub_coag, // in-outs + qaer_cur, qaer_delsub_cond, qaer_delsub_coag, // in-outs + qaer_delsub_coag_in); // in-outs + } // do_aging_in_subarea + + // The following block has to be placed here (after both condensation and + // aging) as both can change the values of qnum_delsub_cond and + // qaer_delsub_cond. + + if(do_cond_sub) { + for(int im = 0; im < nmodes; ++im) { + qnum_delaa[im][iqtend_cond()] = + qnum_delaa[im][iqtend_cond()] + qnum_delsub_cond[im]; + } + for(int is = 0; is < nspecies; ++is) { + for(int im = 0; im < nmodes; ++im) { + qaer_delaa[is][im][iqtend_cond()] = + qaer_delaa[is][im][iqtend_cond()] + qaer_delsub_cond[is][im]; + } + } + } // do_cond_sub + + } // jtsubstep_loop + +} // mam_amicphys_1subarea + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- KOKKOS_INLINE_FUNCTION void mam_amicphys_1gridcell( - const AmicPhysConfig &config, const int nstep, const Real deltat, - const int nsubarea, const int ncldy_subarea, - const bool iscldy_subarea[maxsubarea()], const Real afracsub[maxsubarea()], - const Real temp, const Real pmid, const Real pdel, const Real zmid, - const Real pblh, const Real relhumsub[maxsubarea()], - Real dgn_a[AeroConfig::num_modes()], Real dgn_awet[AeroConfig::num_modes()], - Real wetdens[AeroConfig::num_modes()], - const Real qsub1[AeroConfig::num_gas_ids()][maxsubarea()], - const Real qsub2[AeroConfig::num_gas_ids()][maxsubarea()], - const Real qqcwsub2[AeroConfig::num_gas_ids()][maxsubarea()], - const Real qsub3[AeroConfig::num_gas_ids()][maxsubarea()], - const Real qqcwsub3[AeroConfig::num_gas_ids()][maxsubarea()], - Real qaerwatsub3[AeroConfig::num_modes()][maxsubarea()], - Real qsub4[AeroConfig::num_gas_ids()][maxsubarea()], - Real qqcwsub4[AeroConfig::num_gas_ids()][maxsubarea()], - Real qaerwatsub4[AeroConfig::num_modes()][maxsubarea()], - Real qsub_tendaa[AeroConfig::num_gas_ids()][nqtendaa()][maxsubarea()], - Real qqcwsub_tendaa[AeroConfig::num_gas_ids()][nqqcwtendaa()] - [maxsubarea()]) { + // in + const AmicPhysConfig &config, const Real deltat, const int nsubarea, + const int ncldy_subarea, const bool (&iscldy_subarea)[maxsubarea()], + const Real (&afracsub)[maxsubarea()], const Real temp, const Real pmid, + const Real pdel, const Real zmid, const Real pblh, + const Real (&relhumsub)[maxsubarea()], const Real (&dgn_a)[num_modes], + const Real (&dgn_awet)[num_modes], const Real (&wetdens)[num_modes], + const Real (&qsub1)[gas_pcnst][maxsubarea()], + const Real (&qsub2)[gas_pcnst][maxsubarea()], + const Real (&qqcwsub2)[gas_pcnst][maxsubarea()], + const Real (&qsub3)[gas_pcnst][maxsubarea()], + const Real (&qqcwsub3)[gas_pcnst][maxsubarea()], + const Real (&qaerwatsub3)[num_modes][maxsubarea()], + // out + Real (&qsub4)[gas_pcnst][maxsubarea()], + Real (&qqcwsub4)[gas_pcnst][maxsubarea()], + Real (&qaerwatsub4)[num_modes][maxsubarea()], + Real (&qsub_tendaa)[gas_pcnst][nqtendaa()][maxsubarea()], + Real (&qqcwsub_tendaa)[gas_pcnst][nqqcwtendaa()][maxsubarea()]) { // - // calculates changes to gas and aerosol sub-area TMRs (tracer mixing ratios) - // qsub3 and qqcwsub3 are the incoming current TMRs - // qsub4 and qqcwsub4 are the outgoing updated TMRs + // calculates changes to gas and aerosol sub-area TMRs (tracer mixing + // ratios) qsub3 and qqcwsub3 are the incoming current TMRs qsub4 and + // qqcwsub4 are the outgoing updated TMRs // // qsubN and qqcwsubN (N=1:4) are tracer mixing ratios (TMRs, mol/mol or // #/kmol) in sub-areas @@ -1341,197 +1734,427 @@ void mam_amicphys_1gridcell( // the processes are condensation/evaporation (and associated aging), // renaming, coagulation, and nucleation - static constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - static constexpr int num_modes = AeroConfig::num_modes(); - static constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); - - // the q--4 values will be equal to q--3 values unless they get changed - for(int i = 0; i < num_gas_ids; ++i) - for(int j = 0; j < maxsubarea(); ++j) { + constexpr int mdo_gaexch_cldy_subarea = 0; + // the qq--4 values will be equal to qq--3 values unless they get changed + for(int i = 0; i < num_gas_ids; ++i) { + for(int j = 1; j < maxsubarea(); ++j) { qsub4[i][j] = qsub3[i][j]; qqcwsub4[i][j] = qqcwsub3[i][j]; } - for(int i = 0; i < num_modes; ++i) - for(int j = 0; j < maxsubarea(); ++j) qaerwatsub4[i][j] = qaerwatsub3[i][j]; - for(int i = 0; i < num_gas_ids; ++i) - for(int j = 0; j < nqtendaa(); ++j) - for(int k = 0; k < maxsubarea(); ++k) qsub_tendaa[i][j][k] = 0; - for(int i = 0; i < num_gas_ids; ++i) - for(int j = 0; j < nqqcwtendaa(); ++j) - for(int k = 0; k < maxsubarea(); ++k) qqcwsub_tendaa[i][j][k] = 0.0; - - for(int jsub = 0; jsub < nsubarea; ++jsub) { - AmicPhysConfig sub_config = config; + } + + for(int i = 0; i < num_modes; ++i) { + for(int j = 0; j < maxsubarea(); ++j) { + qaerwatsub4[i][j] = qaerwatsub3[i][j]; + } + } + + assign_3d_array(num_gas_ids, nqtendaa(), maxsubarea(), 0.0, // in + qsub_tendaa); // out + + assign_3d_array(num_gas_ids, nqqcwtendaa(), maxsubarea(), 0.0, // in + qqcwsub_tendaa); // out + + EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), + "Error! mam_amicphys_1gridcell: " + "nsubarea should be < maxsubarea() \n"); + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + bool do_cond; + bool do_rename; + bool do_newnuc; + bool do_coag; if(iscldy_subarea[jsub]) { - sub_config.do_cond = config.do_cond; - sub_config.do_rename = config.do_rename; - sub_config.do_newnuc = false; - sub_config.do_coag = false; + do_cond = config.do_cond; + do_rename = config.do_rename; + do_newnuc = false; + do_coag = false; + if(mdo_gaexch_cldy_subarea <= 0) do_cond = false; + } else { + do_cond = config.do_cond; + do_rename = config.do_rename; + do_newnuc = config.do_newnuc; + do_coag = config.do_coag; } - const bool do_map_gas_sub = sub_config.do_cond || sub_config.do_newnuc; + const bool do_map_gas_sub = do_cond || do_newnuc; // map incoming sub-area mix-ratios to gas/aer/num arrays - Real qgas1[num_gas_ids] = {}; - Real qgas3[num_gas_ids] = {}; - Real qgas4[num_gas_ids] = {}; + Real qgas1[max_gas()] = {0}; + Real qgas2[max_gas()] = {0}; + Real qgas3[max_gas()] = {0}; + Real qgas4[max_gas()] = {0}; + assign_1d_array(max_gas(), 0.0, // in + qgas1); // out + assign_1d_array(max_gas(), 0.0, // in + qgas2); // out + assign_1d_array(max_gas(), 0.0, // in + qgas3); // out + assign_1d_array(max_gas(), 0.0, // in + qgas4); // out + if(do_map_gas_sub) { // for cldy subarea, only do gases if doing gaexch - for(int igas = 0; igas < 2; ++igas) { + for(int igas = 0; igas < max_gas(); ++igas) { const int l = lmap_gas(igas); qgas1[igas] = qsub1[l][jsub] * fcvt_gas(igas); + qgas2[igas] = qsub2[l][jsub] * fcvt_gas(igas); qgas3[igas] = qsub3[l][jsub] * fcvt_gas(igas); qgas4[igas] = qgas3[igas]; } } - Real qaer2[num_aerosol_ids][num_modes] = {}; - Real qaer3[num_aerosol_ids][num_modes] = {}; - Real qnum3[num_modes] = {}; - Real qaer4[num_aerosol_ids][num_modes] = {}; - Real qnum4[num_modes] = {}; - Real qwtr3[num_modes] = {}; - Real qwtr4[num_modes] = {}; - for(int n = 0; n < num_modes; ++n) { - qnum3[n] = qsub3[n][jsub] * fcvt_num(); - qnum4[n] = qnum3[n]; + + Real qaer2[num_aerosol_ids][num_modes] = {0}; + Real qnum2[num_modes] = {0}; + Real qaer3[num_aerosol_ids][num_modes] = {0}; + Real qnum3[num_modes] = {0}; + Real qaer4[num_aerosol_ids][num_modes] = {0}; + Real qnum4[num_modes] = {0}; + Real qwtr3[num_modes] = {0}; + Real qwtr4[num_modes] = {0}; + + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaer2); // out + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaer3); // out + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaer4); // out + + assign_1d_array(num_modes, 0.0, // in + qnum2); // out + assign_1d_array(num_modes, 0.0, // in + qnum3); // out + assign_1d_array(num_modes, 0.0, // in + qnum4); // out + assign_1d_array(num_modes, 0.0, // in + qwtr3); // out + assign_1d_array(num_modes, 0.0, // in + qwtr4); // out + + for(int imode = 0; imode < num_modes; ++imode) { + const int ln = lmap_num(imode); + qnum2[imode] = qsub2[ln][jsub] * fcvt_num(); + qnum3[imode] = qsub3[ln][jsub] * fcvt_num(); + qnum4[imode] = qnum3[imode]; for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - qaer2[iaer][n] = qsub2[iaer][jsub] * fcvt_aer(iaer); - qaer3[iaer][n] = qsub3[iaer][jsub] * fcvt_aer(iaer); - qaer4[iaer][n] = qaer3[iaer][n]; - } - qwtr3[n] = qaerwatsub3[n][jsub] * fcvt_wtr(); - qwtr4[n] = qwtr3[n]; - } - Real qaercw2[num_aerosol_ids][num_modes] = {}; - Real qaercw3[num_aerosol_ids][num_modes] = {}; - Real qnumcw3[num_modes] = {}; - Real qaercw4[num_aerosol_ids][num_modes] = {}; - Real qnumcw4[num_modes] = {}; - if(iscldy_subarea[jsub]) { - // only do cloud-borne for cloudy - for(int n = 0; n < num_modes; ++n) { - qnumcw3[n] = qqcwsub3[n][jsub] * fcvt_num(); - qnumcw4[n] = qnumcw3[n]; - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - qaercw2[iaer][n] = qqcwsub2[n][jsub] * fcvt_aer(iaer); - qaercw3[iaer][n] = qqcwsub3[n][jsub] * fcvt_aer(iaer); - qaercw4[iaer][n] = qaercw3[iaer][n]; + const int la = lmap_aer(iaer, imode); + if(la > 0) { + qaer2[iaer][imode] = qsub2[la][jsub] * fcvt_aer(iaer); + qaer3[iaer][imode] = qsub3[la][jsub] * fcvt_aer(iaer); + qaer4[iaer][imode] = qaer3[iaer][imode]; } - } - } + } // for iaer + qwtr3[imode] = qaerwatsub3[imode][jsub] * fcvt_wtr(); + qwtr4[imode] = qwtr3[imode]; + } // for imode + + Real qaercw2[num_aerosol_ids][num_modes] = {0}; + Real qnumcw2[num_modes] = {0}; + Real qaercw3[num_aerosol_ids][num_modes] = {0}; + Real qnumcw3[num_modes] = {0}; + Real qaercw4[num_aerosol_ids][num_modes] = {0}; + Real qnumcw4[num_modes] = {0}; + + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaercw2); // out + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaercw3); // out + assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in + qaercw4); // out + + assign_1d_array(num_modes, 0.0, // in + qnumcw2); // out + assign_1d_array(num_modes, 0.0, // in + qnumcw3); // out + assign_1d_array(num_modes, 0.0, // in + qnumcw4); // out - Real qgas_delaa[num_gas_ids][nqtendaa()] = {}; + if(iscldy_subarea[jsub]) { + for(int imode = 0; imode < num_modes; ++imode) { + qnumcw2[imode] = 0; + qnumcw3[imode] = 0; + qnumcw4[imode] = 0; + } // imode + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int imode = 0; imode < num_modes; ++imode) { + qaercw2[iaer][imode] = 0; + qaercw3[iaer][imode] = 0; + qaercw4[iaer][imode] = 0; + } // imode + } // iaer + // only do cloud-borne for cloudy + for(int imode = 0; imode < num_modes; ++imode) { + int ln = lmap_numcw(imode); + qnumcw2[imode] = qqcwsub2[ln][jsub] * fcvt_num(); + qnumcw3[imode] = qqcwsub3[ln][jsub] * fcvt_num(); + qnumcw4[imode] = qnumcw3[imode]; + } // imode + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + for(int imode = 0; imode < num_modes; ++imode) { + int la = lmap_aer(iaer, imode); + if(la > 0) { + qaercw2[iaer][imode] = qqcwsub2[la][jsub] * fcvt_aer(iaer); + qaercw3[iaer][imode] = qqcwsub3[la][jsub] * fcvt_aer(iaer); + qaercw4[iaer][imode] = qaercw3[iaer][imode]; + } // la + } // imode + } // iaer + } // iscldy_subarea + + Real qgas_delaa[max_gas()][nqtendaa()] = {}; Real qnum_delaa[num_modes][nqtendaa()] = {}; Real qnumcw_delaa[num_modes][nqqcwtendaa()] = {}; Real qaer_delaa[num_aerosol_ids][num_modes][nqtendaa()] = {}; Real qaercw_delaa[num_aerosol_ids][num_modes][nqqcwtendaa()] = {}; - if(iscldy_subarea[jsub]) { - mam_amicphys_1subarea_cloudy( - sub_config, nstep, deltat, jsub, nsubarea, iscldy_subarea[jsub], - afracsub[jsub], temp, pmid, pdel, zmid, pblh, relhumsub[jsub], dgn_a, - dgn_awet, wetdens, qgas1, qgas3, qgas4, qgas_delaa, qnum3, qnum4, - qnum_delaa, qaer2, qaer3, qaer4, qaer_delaa, qwtr3, qwtr4, qnumcw3, - qnumcw4, qnumcw_delaa, qaercw2, qaercw3, qaercw4, qaercw_delaa); - } else { - mam_amicphys_1subarea_clear( - sub_config, nstep, deltat, jsub, nsubarea, iscldy_subarea[jsub], - afracsub[jsub], temp, pmid, pdel, zmid, pblh, relhumsub[jsub], dgn_a, - dgn_awet, wetdens, qgas1, qgas3, qgas4, qgas_delaa, qnum3, qnum4, - qnum_delaa, qaer3, qaer4, qaer_delaa, qwtr3, qwtr4); - // map gas/aer/num arrays (mix-ratio and del=change) back to sub-area - // arrays - - if(do_map_gas_sub) { - for(int igas = 0; igas < 2; ++igas) { - const int l = lmap_gas(igas); - qsub4[l][jsub] = qgas4[igas] / fcvt_gas(igas); - for(int i = 0; i < nqtendaa(); ++i) - qsub_tendaa[l][i][jsub] = - qgas_delaa[igas][i] / (fcvt_gas(igas) * deltat); + mam_amicphys_1subarea( + // in + config.gaexch_h2so4_uptake_optaa, config.newnuc_h2so4_conc_optaa, + do_cond, do_rename, do_newnuc, do_coag, deltat, jsub, + iscldy_subarea[jsub], afracsub[jsub], temp, pmid, pdel, zmid, pblh, + relhumsub[jsub], dgn_a, dgn_awet, wetdens, qgas1, qgas3, qgas4, + qgas_delaa, // out + qnum3, // in + qnum4, qnum_delaa, // out + qaer2, qaer3, // in + qaer4, qaer_delaa, // out + qwtr3, // in + qwtr4, // out + qnumcw3, // in + qnumcw4, qnumcw_delaa, // out + qaercw2, qaercw3, // in + qaercw4, qaercw_delaa); // out + + // FIXME: Enable this functionality + /*if (nsubarea == 1 || !iscldy_subarea[jsub]) { + ncluster_tend_nnuc_1grid = ncluster_tend_nnuc_1grid & + + + misc_vars_aa_sub(jsub)%ncluster_tend_nnuc_1grid*afracsub(jsub) + }*/ + + // map gas/aer/num arrays (mix-ratio and del=change) back to sub-area arrays + + if(do_map_gas_sub) { + for(int igas = 0; igas < max_gas(); ++igas) { + int ll = lmap_gas(igas); + qsub4[ll][jsub] = qgas4[igas] / fcvt_gas(igas); + for(int jj = 0; jj < nqtendaa(); ++jj) { + qsub_tendaa[ll][jj][jsub] = + qgas_delaa[igas][jj] / (fcvt_gas(igas) * deltat); } + } // igas + } // do_map_gas_sub + + for(int imode = 0; imode < num_modes; ++imode) { + int ll = lmap_num(imode); + qsub4[ll][jsub] = qnum4[imode] / (fcvt_num()); + for(int jj = 0; jj < nqtendaa(); ++jj) { + qsub_tendaa[ll][jj][jsub] = + qnum_delaa[imode][jj] / (fcvt_num() * deltat); } - for(int n = 0; n < num_modes; ++n) { - qsub4[n][jsub] = qnum4[n] / fcvt_num(); - for(int i = 0; i < nqtendaa(); ++i) - qsub_tendaa[n][i][jsub] = qnum_delaa[n][i] / (fcvt_num() * deltat); + for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { + int la = lmap_aer(iaer, imode); + if(la > 0) { + qsub4[la][jsub] = qaer4[iaer][imode] / fcvt_aer(iaer); + for(int jj = 0; jj < nqtendaa(); ++jj) { + qsub_tendaa[la][jj][jsub] = + qaer_delaa[iaer][imode][jj] / (fcvt_aer(iaer) * deltat); + } // jj + } // la + } // iaer + qaerwatsub4[imode][jsub] = qwtr4[imode] / fcvt_wtr(); + + if(iscldy_subarea[jsub]) { + int lc = lmap_numcw(imode); + qqcwsub4[lc][jsub] = qnumcw4[imode] / fcvt_num(); + for(int jj = 0; jj < nqqcwtendaa(); ++jj) { + qqcwsub_tendaa[lc][jj][jsub] = + qnumcw_delaa[imode][jj] / (fcvt_num() * deltat); + } // jj for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - qsub4[iaer][jsub] = qaer4[iaer][n] / fcvt_aer(iaer); - for(int i = 0; i < nqtendaa(); ++i) - qsub_tendaa[iaer][i][jsub] = - qaer_delaa[iaer][n][i] / (fcvt_aer(iaer) * deltat); - } - qaerwatsub4[n][jsub] = qwtr4[n] / fcvt_wtr(); - - if(iscldy_subarea[jsub]) { - qqcwsub4[n][jsub] = qnumcw4[n] / fcvt_num(); - for(int i = 0; i < nqqcwtendaa(); ++i) - qqcwsub_tendaa[n][i][jsub] = - qnumcw_delaa[n][i] / (fcvt_num() * deltat); - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - qqcwsub4[iaer][jsub] = qaercw4[iaer][n] / fcvt_aer(iaer); - for(int i = 0; i < nqqcwtendaa(); ++i) - qqcwsub_tendaa[iaer][i][jsub] = - qaercw_delaa[iaer][n][i] / (fcvt_aer(iaer) * deltat); - } - } + int lca = lmap_aercw(iaer, imode); + if(lca > 0) { + qqcwsub4[lca][jsub] = qaercw4[iaer][imode] / fcvt_aer(iaer); + for(int jj = 0; jj < nqqcwtendaa(); ++jj) { + qqcwsub_tendaa[lca][jj][jsub] = + qaercw_delaa[iaer][imode][jj] / (fcvt_aer(iaer) * deltat); + } // jj + } // lca + } // iaer + } // iscldy_subarea + } // imode + } // main_jsub_loop + +} // mam_amicphys_1gridcell + +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +KOKKOS_INLINE_FUNCTION +void form_gcm_of_gases_and_aerosols_from_subareas( + // in + const int nsubarea, const int ncldy_subarea, + const Real (&afracsub)[maxsubarea()], + const Real (&qsub)[gas_pcnst][maxsubarea()], + const Real (&qqcwsub)[gas_pcnst][maxsubarea()], + const Real (&qqcwgcm_old)[gas_pcnst], + // out + Real (&qgcm)[gas_pcnst], Real (&qqcwgcm)[gas_pcnst]) { + //-------------------------------------------------------------------------- + // Purpose: Form grid cell mean values by calculating area-weighted averages + // of the subareas. + // - For gases and interstitial aerosols, sum over all active + // subareas. + // - For cloud-borne aerosols, + //--------------------------------------------------------------------------- + + // nsubarea: # of active subareas + // ncldy_subarea : # of cloudy subareas + // afracsub(maxsubarea):area fraction of subareas [unitless] + + // The following arguments are mixing ratios. Their units do not matter for + // this subroutine. + + // qsub (ncnst, maxsubarea):gas and interst. aerosol mixing ratios in + // subareas + // qqcwsub(ncnst, maxsubarea): cloud-borne aerosol mixing ratios in + // subareas + // qqcwgcm_old(ncnst): grid cell mean cloud-borne aerosol mixing + // ratios before aerosol microphysics calculations + // qgcm (ncnst): grid cell mean gas and interst. aerosol mixing ratios + // qqcwgcm(ncnst): cloud-borne aerosol mixing ratios in subareas + + // Gases and interstitial aerosols + assign_1d_array(gas_pcnst, 0.0, // in + qgcm); // out + + EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), + "Error! form_gcm_of_gases_and_aerosols_from_subareas: " + "nsubarea should be < maxsubarea() \n"); + + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qgcm[icnst] += qsub[icnst][jsub] * afracsub[jsub]; + } + } + + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qgcm[icnst] = haero::max(0, qgcm[icnst]); + } + + // Cloud-borne aerosols + if(ncldy_subarea <= 0) { + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qqcwgcm[icnst] = qqcwgcm_old[icnst]; + } + } else { + assign_1d_array(gas_pcnst, 0.0, // in + qqcwgcm); // out + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qqcwgcm[icnst] += qqcwsub[icnst][jsub] * afracsub[jsub]; + } + } + } // if ncldy_subarea +} // form_gcm_of_gases_and_aerosols_from_subareas + +//-------------------------------------------------------------------------------- +// Purpose: Get grid cell mean tendencies by calculating area-weighted averages +// of the values in different subareas. +//-------------------------------------------------------------------------------- +KOKKOS_INLINE_FUNCTION +void get_gcm_tend_diags_from_subareas( + // in + const int nsubarea, const int ncldy_subarea, + const Real (&afracsub)[maxsubarea()], + const Real (&qsub_tendaa)[gas_pcnst][nqtendaa()][maxsubarea()], + const Real (&qqcwsub_tendaa)[gas_pcnst][nqqcwtendaa()][maxsubarea()], + // out + Real (&qgcm_tendaa)[gas_pcnst][nqtendaa()], + Real (&qqcwgcm_tendaa)[gas_pcnst][nqqcwtendaa()]) { + // nsubarea: # of active subareas + // ncldy_subarea: # of cloudy subareas + // afracsub(maxsubarea): area fraction of subareas [unitless] + + // Gases and interstitial aerosols + assign_2d_array(gas_pcnst, nqtendaa(), 0.0, // in + qgcm_tendaa); // out + + EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), + "Error! get_gcm_tend_diags_from_subareas: " + "nsubarea should be < maxsubarea() \n"); + + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + for(int iq = 0; iq < nqtendaa(); ++iq) { + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qgcm_tendaa[icnst][iq] += qsub_tendaa[icnst][iq][jsub] * afracsub[jsub]; } } } -} + + // Cloud-borne aerosols + + assign_2d_array(gas_pcnst, nqqcwtendaa(), 0.0, // in + qqcwgcm_tendaa); // out + if(ncldy_subarea > 0) { + for(int jsub = 1; jsub <= nsubarea; ++jsub) { + for(int iq = 0; iq < nqqcwtendaa(); ++iq) { + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + qqcwgcm_tendaa[icnst][iq] += + qqcwsub_tendaa[icnst][iq][jsub] * afracsub[jsub]; + } + } + } + } // if (ncldy_subarea +} // get_gcm_tend_diags_from_subareas } // anonymous namespace +//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + KOKKOS_INLINE_FUNCTION -void modal_aero_amicphys_intr(const AmicPhysConfig &config, const int nstep, - const Real deltat, const Real temp, - const Real pmid, const Real pdel, const Real zm, - const Real pblh, const Real qv, const Real cld, - Real q[gas_pcnst()], Real qqcw[gas_pcnst()], - const Real q_pregaschem[gas_pcnst()], - const Real q_precldchem[gas_pcnst()], - const Real qqcw_precldchem[gas_pcnst()], - Real q_tendbb[gas_pcnst()][nqtendbb()], - Real qqcw_tendbb[gas_pcnst()][nqtendbb()], - Real dgncur_a[AeroConfig::num_modes()], - Real dgncur_awet[AeroConfig::num_modes()], - Real wetdens_host[AeroConfig::num_modes()], - Real qaerwat[AeroConfig::num_modes()]) { - /* - nstep ! model time-step number - nqtendbb ! dimension for q_tendbb - nqqcwtendbb ! dimension f - deltat ! - q(ncol,pver,pcnstxx) ! current tracer mixing ratios (TMRs) - these values are updated (so out /= in) - *** MUST BE #/kmol-air for number - *** MUST BE mol/mol-air for mass - *** NOTE ncol dimension - qqcw(ncol,pver,pcnstxx) - like q but for cloud-borner tracers - these values are updated - q_pregaschem(ncol,pver,pcnstxx) ! q TMRs before gas-phase - chemistry q_precldchem(ncol,pver,pcnstxx) ! q TMRs before cloud - chemistry qqcw_precldchem(ncol,pver,pcnstxx) ! qqcw TMRs before cloud - chemistry q_tendbb(ncol,pver,pcnstxx,nqtendbb()) ! TMR tendencies for - box-model diagnostic output qqcw_tendbb(ncol,pver,pcnstx t(pcols,pver) ! - temperature at model levels (K) pmid(pcols,pver) ! pressure at model - level centers (Pa) pdel(pcols,pver) ! pressure thickness of levels - (Pa) zm(pcols,pver) ! altitude (above ground) at level centers (m) - pblh(pcols) ! planetary boundary layer depth (m) - qv(pcols,pver) ! specific humidity (kg/kg) - cld(ncol,pver) ! cloud fraction (-) *** NOTE ncol dimension - dgncur_a(pcols,pver,ntot_amode) - dgncur_awet(pcols,pver,ntot_amode) - ! dry & wet geo. mean dia. (m) of - number distrib. wetdens_host(pcols,pver,ntot_amode) ! interstitial - aerosol wet density (kg/m3) - - qaerwat(pcols,pver,ntot_amode aerosol water mixing ratio (kg/kg, - NOT mol/mol) - - */ - - // !DESCRIPTION: +void modal_aero_amicphys_intr( + // in + const AmicPhysConfig &config, const Real deltat, const Real temp, + const Real pmid, const Real pdel, const Real zm, const Real pblh, + const Real qv, const Real cld, + // out + Real qq[gas_pcnst], Real qqcw[gas_pcnst], + // in + const Real (&q_pregaschem)[gas_pcnst], + const Real (&q_precldchem)[gas_pcnst], + const Real (&qqcw_precldchem)[gas_pcnst], const Real (&dgncur_a)[num_modes], + const Real (&dgncur_awet)[num_modes], + const Real (&wetdens_host)[num_modes]) { + // deltat: time step + // qq(ncol,pver,pcnst): current tracer mixing ratios (TMRs) + // these values are updated (so out /= in) + // *** MUST BE #/kmol-air for number + // *** MUST BE mol/mol-air for mass + // *** NOTE ncol dimension + // qqcw(ncol,pver,pcnst): + // like qq but for cloud-borner tracers + // these values are updated + // q_pregaschem(ncol,pver,pcnst): qq TMRs before gas-phase + // chemistry + // + // q_precldchem(ncol,pver,pcnstxx): qq TMRs before cloud + // chemistry + // qqcw_precldchem(ncol,pver,pcnstxx): qqcw TMRs before cloud + // chemistry + // t(pcols,pver): temperature at model levels (K) + // pmid(pcols,pver):pressure at model level centers (Pa) + // pdel(pcols,pver):pressure thickness of levels (Pa) + // zm(pcols,pver) :altitude (above ground) at level centers (m) + // pblh(pcols) :planetary boundary layer depth (m) + // qv(pcols,pver) :specific humidity (kg/kg) + // cld(ncol,pver) :cloud fraction (-) *** NOTE ncol dimension + // dgncur_a(pcols,pver,ntot_amode) + // dgncur_awet(pcols,pver,ntot_amode) + // :dry & wet geo. mean dia. (m) of + // number distrib. wetdens_host(pcols,pver,ntot_amode): interstitial + // aerosol wet density (kg/m3) + + // DESCRIPTION: // calculates changes to gas and aerosol TMRs (tracer mixing ratios) from // gas-aerosol exchange (condensation/evaporation) // growth from smaller to larger modes (renaming) due to both @@ -1542,16 +2165,14 @@ void modal_aero_amicphys_intr(const AmicPhysConfig &config, const int nstep, // (aging) // due to condensation and coagulation // - // the incoming mixing ratios (q and qqcw) are updated before output + // the incoming mixing ratios (qq and qqcw) are updated before output // - // !REVISION HISTORY: + // REVISION HISTORY: // RCE 07.04.13: Adapted from earlier version of CAM5 modal aerosol // routines // for these processes // - static constexpr int num_modes = AeroConfig::num_modes(); - // qgcmN and qqcwgcmN (N=1:4) are grid-cell mean tracer mixing ratios // (TMRs, mol/mol or #/kmol) // N=1 - before gas-phase chemistry @@ -1563,149 +2184,172 @@ void modal_aero_amicphys_intr(const AmicPhysConfig &config, const int nstep, // currently there are just clear and cloudy sub-areas // the N=1:4 have same meanings as for qgcmN - // q_coltendaa and qqcw_coltendaa are column-integrated tendencies - // for different processes, which are output to history - // the processes are condensation/evaporation (and associated aging), - // renaming, coagulation, and nucleation - - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqtendbb(); ++j) - q_tendbb[i][j] = 0.0, qqcw_tendbb[i][j] = 0.0; - - // get saturation mixing ratio - // call qsat( t(1:ncol,1:pver), pmid(1:ncol,1:pvnner), & - // ev_sat(1:ncol,1:pver), qv_sat(1:ncol,1:pver) ) + // Compute saturation vapor pressure const Real epsqs = haero::Constants::weight_ratio_h2o_air; + // Saturation vapor pressure const Real ev_sat = conversions::vapor_saturation_pressure_magnus(temp, pmid); + // Saturation specific humidity const Real qv_sat = epsqs * ev_sat / (pmid - (1 - epsqs) * ev_sat); - const Real relhumgcm = haero::max(0.0, haero::min(1.0, qv / qv_sat)); + // total # of subareas to do calculations for + int nsubarea; + + // total # of cloudy subareas + int ncldy_subarea; - // Set up cloudy/clear subareas inside a grid cell - int nsubarea, ncldy_subarea, jclea, jcldy; + // indices of the clear and cloudy subareas + int jclea, jcldy; + + // whether a subarea is cloudy bool iscldy_subarea[maxsubarea()]; + + // area fraction of each active subarea[unitless] Real afracsub[maxsubarea()]; + + // cloudy and clear fractions of the grid cell + Real fcldy, fclea; + + setup_subareas(cld, // in + nsubarea, ncldy_subarea, jclea, jcldy, // out + iscldy_subarea, afracsub, fclea, fcldy); // out + EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), + "Error! modal_aero_amicphys_intr: " + "nsubarea should be < maxsubarea() \n"); + + const Real relhumgcm = haero::max(0.0, haero::min(1.0, qv / qv_sat)); + Real relhumsub[maxsubarea()]; - Real qsub1[gas_pcnst()][maxsubarea()]; - Real qsub2[gas_pcnst()][maxsubarea()]; - Real qsub3[gas_pcnst()][maxsubarea()]; - Real qqcwsub1[gas_pcnst()][maxsubarea()]; - Real qqcwsub2[gas_pcnst()][maxsubarea()]; - Real qqcwsub3[gas_pcnst()][maxsubarea()]; - // aerosol water mixing ratios (mol/mol) - Real qaerwatsub3[AeroConfig::num_modes()][maxsubarea()]; - construct_subareas_1gridcell(cld, relhumgcm, // in - q_pregaschem, q_precldchem, // in - qqcw_precldchem, // in - q, qqcw, // in - nsubarea, ncldy_subarea, jclea, jcldy, // out - iscldy_subarea, afracsub, relhumsub, // out - qsub1, qsub2, qsub3, // out - qqcwsub1, qqcwsub2, qqcwsub3, - qaerwatsub3, // out - qaerwat // in - ); + set_subarea_rh(ncldy_subarea, jclea, jcldy, afracsub, relhumgcm, // in + relhumsub); // out + + //------------------------------- + // Set aerosol water in subareas + //------------------------------- + // Notes from Dick Easter/Steve Ghan: how to treat aerosol water in + // subareas needs more work/thinking Currently modal_aero_water_uptake + // calculates qaerwat using the grid-cell mean interstital-aerosol + // mix-rates and the clear-area RH. aerosol water mixing ratios (mol/mol) + Real qaerwatsub3[num_modes][maxsubarea()]; + assign_2d_array(num_modes, maxsubarea(), 0.0, // in + qaerwatsub3); // out + + //------------------------------------------------------------------------- + // Set gases, interstitial aerosols, and cloud-borne aerosols in subareas + //------------------------------------------------------------------------- + // Copy grid cell mean mixing ratios; clip negative values if any. + Real qgcm1[gas_pcnst], qgcm2[gas_pcnst], qgcm3[gas_pcnst]; + Real qqcwgcm2[gas_pcnst], qqcwgcm3[gas_pcnst]; // cld borne aerosols + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + // Gases and interstitial aerosols + qgcm1[icnst] = haero::max(0, q_pregaschem[icnst]); + qgcm2[icnst] = haero::max(0, q_precldchem[icnst]); + qgcm3[icnst] = haero::max(0, qq[icnst]); + + // Cloud-borne aerosols + qqcwgcm2[icnst] = haero::max(0, qqcw_precldchem[icnst]); + qqcwgcm3[icnst] = haero::max(0, qqcw[icnst]); + } + // Partition grid cell mean to subareas + Real qsub1[gas_pcnst][maxsubarea()]; + Real qsub2[gas_pcnst][maxsubarea()]; + Real qsub3[gas_pcnst][maxsubarea()]; + Real qqcwsub1[gas_pcnst][maxsubarea()]; + Real qqcwsub2[gas_pcnst][maxsubarea()]; + Real qqcwsub3[gas_pcnst][maxsubarea()]; + + set_subarea_gases_and_aerosols(nsubarea, jclea, jcldy, fclea, // in + fcldy, // in + qgcm1, qgcm2, qqcwgcm2, qgcm3, // in + qqcwgcm3, // in + qsub1, qsub2, qqcwsub2, qsub3, // out + qqcwsub3); // out // Initialize the "after-amicphys" values - Real qsub4[gas_pcnst()][maxsubarea()] = {}; - Real qqcwsub4[gas_pcnst()][maxsubarea()] = {}; - Real qaerwatsub4[AeroConfig::num_modes()][maxsubarea()] = {}; + Real qsub4[gas_pcnst][maxsubarea()] = {}; + Real qqcwsub4[gas_pcnst][maxsubarea()] = {}; + Real qaerwatsub4[num_modes][maxsubarea()] = {}; // // start integration // - Real dgn_a[num_modes], dgn_awet[num_modes], wetdens[num_modes]; - for(int n = 0; n < num_modes; ++n) { - dgn_a[n] = dgncur_a[n]; - dgn_awet[n] = dgncur_awet[n]; - wetdens[n] = haero::max(1000.0, wetdens_host[n]); + Real dgn_a[num_modes] = {}; + Real dgn_awet[num_modes] = {}; + Real wetdens[num_modes] = {}; + assign_1d_array(num_modes, 0.0, // in + dgn_a); // out + assign_1d_array(num_modes, 0.0, // in + dgn_awet); // out + assign_1d_array(num_modes, 1000.0, // in + wetdens); // out + + for(int imode = 0; imode < num_modes; ++imode) { + dgn_a[imode] = dgncur_a[imode]; + dgn_awet[imode] = dgncur_awet[imode]; + wetdens[imode] = haero::max(1000.0, wetdens_host[imode]); } - Real qsub_tendaa[gas_pcnst()][nqtendaa()][maxsubarea()] = {}; - Real qqcwsub_tendaa[gas_pcnst()][nqqcwtendaa()][maxsubarea()] = {}; - mam_amicphys_1gridcell(config, nstep, deltat, nsubarea, ncldy_subarea, - iscldy_subarea, afracsub, temp, pmid, pdel, zm, pblh, - relhumsub, dgn_a, dgn_awet, wetdens, qsub1, qsub2, - qqcwsub2, qsub3, qqcwsub3, qaerwatsub3, qsub4, - qqcwsub4, qaerwatsub4, qsub_tendaa, qqcwsub_tendaa); - // - // form new grid-mean mix-ratios - Real qgcm4[gas_pcnst()]; - Real qgcm_tendaa[gas_pcnst()][nqtendaa()]; - Real qaerwatgcm4[num_modes]; - if(nsubarea == 1) { - for(int i = 0; i < gas_pcnst(); ++i) qgcm4[i] = qsub4[i][0]; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqtendaa(); ++j) - qgcm_tendaa[i][j] = qsub_tendaa[i][j][0]; - for(int i = 0; i < num_modes; ++i) qaerwatgcm4[i] = qaerwatsub4[i][0]; - } else { - for(int i = 0; i < gas_pcnst(); ++i) qgcm4[i] = 0.0; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqtendaa(); ++j) qgcm_tendaa[i][j] = 0.0; - for(int n = 0; n < nsubarea; ++n) { - for(int i = 0; i < gas_pcnst(); ++i) - qgcm4[i] += qsub4[i][n] * afracsub[n]; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqtendaa(); ++j) - qgcm_tendaa[i][j] = - qgcm_tendaa[i][j] + qsub_tendaa[i][j][n] * afracsub[n]; + Real qsub_tendaa[gas_pcnst][nqtendaa()][maxsubarea()] = {}; + Real qqcwsub_tendaa[gas_pcnst][nqqcwtendaa()][maxsubarea()] = {}; + mam_amicphys_1gridcell( + // in + config, deltat, nsubarea, ncldy_subarea, iscldy_subarea, afracsub, temp, + pmid, pdel, zm, pblh, relhumsub, dgn_a, dgn_awet, wetdens, qsub1, qsub2, + qqcwsub2, qsub3, qqcwsub3, + // inout + qaerwatsub3, qsub4, qqcwsub4, qaerwatsub4, qsub_tendaa, qqcwsub_tendaa); + + //================================================================================================= + // Aerosol microphysics calculations done for all subareas. Form new grid cell + // mean mixing ratios. + //================================================================================================= + // Gases and aerosols + //---------------------- + // Calculate new grid cell mean values + Real qgcm4[gas_pcnst]; + Real qqcwgcm4[gas_pcnst]; + + form_gcm_of_gases_and_aerosols_from_subareas( + // in + nsubarea, ncldy_subarea, afracsub, qsub4, qqcwsub4, qqcwgcm3, + // out + qgcm4, qqcwgcm4); + + // Copy grid cell mean values to output arrays + for(int icnst = 0; icnst < gas_pcnst; ++icnst) { + if(lmapcc_all(icnst) > 0) { + qq[icnst] = qgcm4[icnst]; } - for(int i = 0; i < num_modes; ++i) - // for aerosol water use the clear sub-area value - qaerwatgcm4[i] = qaerwatsub4[i][jclea - 1]; - } - Real qqcwgcm4[gas_pcnst()]; - Real qqcwgcm_tendaa[gas_pcnst()][nqqcwtendaa()]; - if(ncldy_subarea <= 0) { - for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = haero::max(0.0, qqcw[i]); - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] = 0.0; - } else if(nsubarea == 1) { - for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = qqcwsub4[i][0]; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqqcwtendaa(); ++j) - qqcwgcm_tendaa[i][j] = qqcwsub_tendaa[i][j][0]; - } else { - for(int i = 0; i < gas_pcnst(); ++i) qqcwgcm4[i] = 0.0; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqqcwtendaa(); ++j) qqcwgcm_tendaa[i][j] = 0.0; - for(int n = 0; n < nsubarea; ++n) { - if(iscldy_subarea[n]) { - for(int i = 0; i < gas_pcnst(); ++i) - qqcwgcm4[i] += qqcwsub4[i][n] * afracsub[n]; - for(int i = 0; i < gas_pcnst(); ++i) - for(int j = 0; j < nqqcwtendaa(); ++j) - qqcwgcm_tendaa[i][j] += qqcwsub_tendaa[i][j][n] * afracsub[n]; - } + if(lmapcc_all(icnst) >= lmapcc_val_aer()) { + qqcw[icnst] = qqcwgcm4[icnst]; } } - for(int lmz = 0; lmz < gas_pcnst(); ++lmz) { - if(lmapcc_all(lmz) > 0) { - // HW, to ensure non-negative - q[lmz] = haero::max(qgcm4[lmz], 0.0); - if(lmapcc_all(lmz) >= lmapcc_val_aer()) { - // HW, to ensure non-negative - qqcw[lmz] = haero::max(qqcwgcm4[lmz], 0.0); - } - } - } - for(int i = 0; i < gas_pcnst(); ++i) { - if(iqtend_cond() < nqtendbb()) - q_tendbb[i][iqtend_cond()] = qgcm_tendaa[i][iqtend_cond()]; - if(iqtend_rnam() < nqtendbb()) - q_tendbb[i][iqtend_rnam()] = qgcm_tendaa[i][iqtend_rnam()]; - if(iqtend_nnuc() < nqtendbb()) - q_tendbb[i][iqtend_nnuc()] = qgcm_tendaa[i][iqtend_nnuc()]; - if(iqtend_coag() < nqtendbb()) - q_tendbb[i][iqtend_coag()] = qgcm_tendaa[i][iqtend_coag()]; - if(iqqcwtend_rnam() < nqqcwtendbb()) - qqcw_tendbb[i][iqqcwtend_rnam()] = qqcwgcm_tendaa[i][iqqcwtend_rnam()]; - } - for(int i = 0; i < num_modes; ++i) qaerwat[i] = qaerwatgcm4[i]; -} + //================================================================ + // Process diagnostics of the current grid cell + //================================================================ + Real qgcm_tendaa[gas_pcnst][nqtendaa()]; + Real qqcwgcm_tendaa[gas_pcnst][nqqcwtendaa()]; + get_gcm_tend_diags_from_subareas( + // in + nsubarea, ncldy_subarea, afracsub, qsub_tendaa, qqcwsub_tendaa, + // out + qgcm_tendaa, qqcwgcm_tendaa); + +#if 0 + //This code is for diagnostics only + // Get gravity + using C = physics::Constants; + static constexpr auto gravit = C::gravit; // Gravity [m/s2] + +accumulate_column_tend_integrals( pdel, gravit, // in + qgcm_tendaa, qqcwgcm_tendaa, // in + q_coltendaa(ii,:,:), qqcw_coltendaa(ii,:,:) )// inout + +ncluster_3dtend_nnuc(ii,kk) = misc_vars_aa%ncluster_tend_nnuc_1grid + +#endif +} // modal_aero_amicphys_intr } // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/mam_coupling.hpp b/components/eamxx/src/physics/mam/mam_coupling.hpp index 4b4bb0043cf..4e116fc5678 100644 --- a/components/eamxx/src/physics/mam/mam_coupling.hpp +++ b/components/eamxx/src/physics/mam/mam_coupling.hpp @@ -810,99 +810,7 @@ void copy_view_lev_slice(haero::ThreadTeamPolicy team_policy, //inputs }); }); } - -// Because CUDA C++ doesn't allow us to declare and use constants outside of -// KOKKOS_INLINE_FUNCTIONS, we define this macro that allows us to (re)define -// these constants where needed within two such functions so we don't define -// them inconsistently. Yes, it's the 21st century and we're still struggling -// with these basic things. -#define DECLARE_PROG_TRANSFER_CONSTANTS \ - /* mapping of constituent indices to aerosol modes */ \ - const auto Accum = mam4::ModeIndex::Accumulation; \ - const auto Aitken = mam4::ModeIndex::Aitken; \ - const auto Coarse = mam4::ModeIndex::Coarse; \ - const auto PC = mam4::ModeIndex::PrimaryCarbon; \ - const auto NoMode = mam4::ModeIndex::None; \ - static const mam4::ModeIndex mode_for_cnst[gas_pcnst()] = { \ - NoMode, NoMode, NoMode, NoMode, NoMode, NoMode, /* gases (not aerosols) */ \ - Accum, Accum, Accum, Accum, Accum, Accum, Accum, Accum, /* 7 aero species + NMR */ \ - Aitken, Aitken, Aitken, Aitken, Aitken, /* 4 aero species + NMR */ \ - Coarse, Coarse, Coarse, Coarse, Coarse, Coarse, Coarse, Coarse, /* 7 aero species + NMR */ \ - PC, PC, PC, PC, /* 3 aero species + NMR */ \ - }; \ - /* mapping of constituent indices to aerosol species */ \ - const auto SOA = mam4::AeroId::SOA; \ - const auto SO4 = mam4::AeroId::SO4; \ - const auto POM = mam4::AeroId::POM; \ - const auto BC = mam4::AeroId::BC; \ - const auto NaCl = mam4::AeroId::NaCl; \ - const auto DST = mam4::AeroId::DST; \ - const auto MOM = mam4::AeroId::MOM; \ - const auto NoAero = mam4::AeroId::None; \ - static const mam4::AeroId aero_for_cnst[gas_pcnst()] = { \ - NoAero, NoAero, NoAero, NoAero, NoAero, NoAero, /* gases (not aerosols) */ \ - SO4, POM, SOA, BC, DST, NaCl, MOM, NoAero, /* accumulation mode */ \ - SO4, SOA, NaCl, MOM, NoAero, /* aitken mode */ \ - DST, NaCl, SO4, BC, POM, SOA, MOM, NoAero, /* coarse mode */ \ - POM, BC, MOM, NoAero, /* primary carbon mode */ \ - }; \ - /* mapping of constituent indices to gases */ \ - const auto O3 = mam4::GasId::O3; \ - const auto H2O2 = mam4::GasId::H2O2; \ - const auto H2SO4 = mam4::GasId::H2SO4; \ - const auto SO2 = mam4::GasId::SO2; \ - const auto DMS = mam4::GasId::DMS; \ - const auto SOAG = mam4::GasId::SOAG; \ - const auto NoGas = mam4::GasId::None; \ - static const mam4::GasId gas_for_cnst[gas_pcnst()] = { \ - O3, H2O2, H2SO4, SO2, DMS, SOAG, \ - NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, \ - NoGas, NoGas, NoGas, NoGas, NoGas, \ - NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, NoGas, \ - NoGas, NoGas, NoGas, NoGas, \ - }; - -// Given a Prognostics object, transfers data for interstitial aerosols to the -// chemistry work array q, and cloudborne aerosols to the chemistry work array -// qqcw, both at vertical level k. The input and output quantities are stored as -// number/mass mixing ratios. -// NOTE: this mapping is chemistry-mechanism-specific (see mo_sim_dat.F90 -// NOTE: in the relevant preprocessed chemical mechanism) -// NOTE: see mam4xx/aero_modes.hpp to interpret these mode/aerosol/gas -// NOTE: indices -KOKKOS_INLINE_FUNCTION -void transfer_prognostics_to_work_arrays(const mam4::Prognostics &progs, - const int k, - Real q[gas_pcnst()], - Real qqcw[gas_pcnst()]) { - DECLARE_PROG_TRANSFER_CONSTANTS - - // copy number/mass mixing ratios from progs to q and qqcw at level k, - // converting them to VMR - for (int i = 0; i < gas_pcnst(); ++i) { - auto mode_index = mode_for_cnst[i]; - auto aero_id = aero_for_cnst[i]; - auto gas_id = gas_for_cnst[i]; - if (gas_id != NoGas) { // constituent is a gas - int g = static_cast(gas_id); - q[i] = progs.q_gas[g](k); - qqcw[i] = progs.q_gas[g](k); - } else { - int m = static_cast(mode_index); - if (aero_id != NoAero) { // constituent is an aerosol species - int a = aerosol_index_for_mode(mode_index, aero_id); - q[i] = progs.q_aero_i[m][a](k); - qqcw[i] = progs.q_aero_c[m][a](k); - } else { // constituent is a modal number mixing ratio - int m = static_cast(mode_index); - q[i] = progs.n_mode_i[m](k); - qqcw[i] = progs.n_mode_c[m](k); - } - } - } -} - -// FIXME: check if we have ported these function in mam4xx. If no, let's move there. +// FIXME: check if we have ported these function in mam4xx. If no, let's move them there. KOKKOS_INLINE_FUNCTION void mmr2vmr(const Real q[gas_pcnst()],//in const Real mw[gas_pcnst()],//in @@ -923,105 +831,6 @@ for (int i = 0; i < gas_pcnst(); ++i) { } }//mmr2vmr -// converts the quantities in the work arrays q and qqcw from mass/number -// mixing ratios to volume/number mixing ratios -KOKKOS_INLINE_FUNCTION -void convert_work_arrays_to_vmr(const Real q[gas_pcnst()], - const Real qqcw[gas_pcnst()], - Real vmr[gas_pcnst()], - Real vmrcw[gas_pcnst()]) { - DECLARE_PROG_TRANSFER_CONSTANTS - - for (int i = 0; i < gas_pcnst(); ++i) { - auto mode_index = mode_for_cnst[i]; - auto aero_id = aero_for_cnst[i]; - auto gas_id = gas_for_cnst[i]; - if (gas_id != NoGas) { // constituent is a gas - int g = static_cast(gas_id); - const Real mw = mam4::gas_species(g).molecular_weight; - vmr[i] = mam4::conversions::vmr_from_mmr(q[i], mw); - vmrcw[i] = mam4::conversions::vmr_from_mmr(qqcw[i], mw); - } else { - if (aero_id != NoAero) { // constituent is an aerosol species - int a = aerosol_index_for_mode(mode_index, aero_id); - const Real mw = mam4::aero_species(a).molecular_weight; - vmr[i] = mam4::conversions::vmr_from_mmr(q[i], mw); - vmrcw[i] = mam4::conversions::vmr_from_mmr(qqcw[i], mw); - } else { // constituent is a modal number mixing ratio - vmr[i] = q[i]; - vmrcw[i] = qqcw[i]; - } - } - } -} - -// converts the quantities in the work arrays vmrs and vmrscw from mass/number -// mixing ratios to volume/number mixing ratios -KOKKOS_INLINE_FUNCTION -void convert_work_arrays_to_mmr(const Real vmr[gas_pcnst()], - const Real vmrcw[gas_pcnst()], - Real q[gas_pcnst()], - Real qqcw[gas_pcnst()]) { - DECLARE_PROG_TRANSFER_CONSTANTS - - for (int i = 0; i < gas_pcnst(); ++i) { - auto mode_index = mode_for_cnst[i]; - auto aero_id = aero_for_cnst[i]; - auto gas_id = gas_for_cnst[i]; - if (gas_id != NoGas) { // constituent is a gas - int g = static_cast(gas_id); - const Real mw = mam4::gas_species(g).molecular_weight; - q[i] = mam4::conversions::mmr_from_vmr(vmr[i], mw); - qqcw[i] = mam4::conversions::mmr_from_vmr(vmrcw[i], mw); - } else { - if (aero_id != NoAero) { // constituent is an aerosol species - int a = aerosol_index_for_mode(mode_index, aero_id); - const Real mw = mam4::aero_species(a).molecular_weight; - q[i] = mam4::conversions::mmr_from_vmr(vmr[i], mw); - qqcw[i] = mam4::conversions::mmr_from_vmr(vmrcw[i], mw); - } else { // constituent is a modal number mixing ratio - q[i] = vmr[i]; - qqcw[i] = vmrcw[i]; - } - } - } -} - -// Given work arrays with interstitial and cloudborne aerosol data, transfers -// them to the given Prognostics object at the kth vertical level. This is the -// "inverse operator" for transfer_prognostics_to_work_arrays, above. -KOKKOS_INLINE_FUNCTION -void transfer_work_arrays_to_prognostics(const Real q[gas_pcnst()], - const Real qqcw[gas_pcnst()], - mam4::Prognostics &progs, const int k) { - DECLARE_PROG_TRANSFER_CONSTANTS - - // copy number/mass mixing ratios from progs to q and qqcw at level k, - // converting them to VMR - for (int i = 0; i < gas_pcnst(); ++i) { - auto mode_index = mode_for_cnst[i]; - auto aero_id = aero_for_cnst[i]; - auto gas_id = gas_for_cnst[i]; - if (gas_id != NoGas) { // constituent is a gas - int g = static_cast(gas_id); - progs.q_gas[g](k) = q[i]; - } else { - int m = static_cast(mode_index); - if (aero_id != NoAero) { // constituent is an aerosol species - int a = aerosol_index_for_mode(mode_index, aero_id); - progs.q_aero_i[m][a](k) = q[i]; - progs.q_aero_c[m][a](k) = qqcw[i]; - } else { // constituent is a modal number mixing ratio - int m = static_cast(mode_index); - progs.n_mode_i[m](k) = q[i]; - progs.n_mode_c[m](k) = qqcw[i]; - } - } - } -} - -#undef DECLARE_PROG_TRANSFER_CONSTANTS - } // namespace scream::mam_coupling #endif diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt index 700f8fffc0f..e1ba85a685f 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt +++ b/components/eamxx/tests/single-process/mam/aero_microphys/CMakeLists.txt @@ -26,7 +26,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/output.yaml set (TEST_INPUT_FILES scream/init/${EAMxx_tests_IC_FILE_MAM4xx_72lev} scream/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc - scream/mam4xx/invariants/ne2np4/oxid_1.9x2.5_L26_1850-2015_ne2np4L72_c20240722_OD.nc + scream/mam4xx/invariants/ne2np4/oxid_ne2np4_L26_1850-2015_c20240827.nc scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml index c5aa98e515a..388781fb8f0 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/output.yaml @@ -6,7 +6,12 @@ Fields: Physics: Field Names: - T_mid - + # To save these fields make sure to turn on + # OUTPUT_TRACER_FIELDS + #- oxi_fields + #- linoz_fields + #- vertical_emission_fields + output_control: Frequency: 1 frequency_units: nsteps diff --git a/externals/mam4xx b/externals/mam4xx index 2eee1988b1e..4b429ed3dde 160000 --- a/externals/mam4xx +++ b/externals/mam4xx @@ -1 +1 @@ -Subproject commit 2eee1988b1e6488c904e0a28564bdcadfa190d34 +Subproject commit 4b429ed3dde2c3c56bed64674d908b9cd323f2a7 From 5e0032fff41f4211be402c4eb19615766658c9ab Mon Sep 17 00:00:00 2001 From: Balwinder Singh Date: Tue, 8 Oct 2024 18:01:39 -0700 Subject: [PATCH 22/31] Adds emissions file for ne4pg2, removes qaerwat from the input.yaml for single process test Changes dgncur_a to dgnum to match namelist entry add files for ne30 elevated emissions Replaces dgncur_a to dgnum in input.yaml, fixes file paths for ne30 emission files and some cosmetic changes Adds ne30pg2 linoz and oxid files Fixes a bug with GNU and ne4 sim on PM GNU Adds linoz src sink function and namelist variables plus code cleanup Swaps linoz and oxid file ordering so that the right file is picked in ne4 sims Changes dims for dgnum in aci interface Changes mam4xx function signatures --- .../cime_config/namelist_defaults_scream.xml | 58 +- .../mam/eamxx_mam_aci_process_interface.cpp | 139 +- ...mxx_mam_microphysics_process_interface.cpp | 749 ++---- ...mxx_mam_microphysics_process_interface.hpp | 189 +- .../eamxx_mam_wetscav_process_interface.cpp | 4 +- .../mam/impl/compute_o3_column_density.cpp | 51 - .../src/physics/mam/impl/helper_micro.hpp | 42 +- .../src/physics/mam/impl/mam4_amicphys.cpp | 2355 ----------------- ...se_chemistry.cpp => photo_table_utils.cpp} | 119 +- .../eamxx/src/physics/mam/mam_coupling.hpp | 20 - .../mam/aero_microphys/input.yaml | 16 +- externals/mam4xx | 2 +- 12 files changed, 526 insertions(+), 3218 deletions(-) delete mode 100644 components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp delete mode 100644 components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp rename components/eamxx/src/physics/mam/impl/{gas_phase_chemistry.cpp => photo_table_utils.cpp} (59%) diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 2882032ae75..1b58c6ed5fc 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -271,14 +271,50 @@ be lost if SCREAM_HACK_XML is not enabled. - ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne4pg2/oxid_ne4pg2_L26_1850-2015_c20240821.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc - 20100101 - ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_bc_a4_elev_ne4pg2_2010_clim_c20240821.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so2_elev_ne4pg2_2010_clim_c20240821.nc + + true + true + true + true + + 172800.0 + 3.0E-008 + 4 + 20100101 + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne30pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne30pg2_c20240724.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc + + 20150101 + ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne30pg2/oxid_1.9x2.5_L26_1850-2015_ne30pg2_c20241009.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne4pg2/oxid_1.9x2.5_L26_1850-2015_ne4pg2_c20241009.nc + 20100101 + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + + 20100101 + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne30pg2_c20241008.nc + + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne4pg2_c20241008.nc @@ -589,9 +625,9 @@ be lost if SCREAM_HACK_XML is not enabled. 0.0 0.0,0.0 - 2.6e-08 - 0.41417721820867320E-007 - 0.15100083211582764E+004 + 1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08 + 1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08 + 5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22 0.0 0.0 0.0 diff --git a/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp index f2d58c54e44..c7ad993e974 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp @@ -63,11 +63,13 @@ void MAMAci::set_grids( // Define the different field layouts that will be used for this process using namespace ShortFieldTagsNames; - // Layout for 3D (2d horiz X 1d vertical) variables - // mid points - FieldLayout scalar3d_layout_mid{{COL, LEV}, {ncol_, nlev_}}; + // Layout for 2D (2d horiz) variable + const FieldLayout scalar2d = grid_->get_2d_scalar_layout(); + + // Layout for 3D (2d horiz X 1d vertical) variable defined at mid-level and // interfaces - FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; + const FieldLayout scalar3d_mid = grid_->get_3d_scalar_layout(true); + const FieldLayout scalar3d_int = grid_->get_3d_scalar_layout(false); // layout for 2D (1d horiz X 1d vertical) variable FieldLayout scalar2d_layout_col{{COL}, {ncol_}}; @@ -79,10 +81,10 @@ void MAMAci::set_grids( }; using namespace ekat::units; - auto q_unit = kg / kg; // units of mass mixing ratios of tracers - auto n_unit = 1 / kg; // units of number mixing ratios of tracers + constexpr auto q_unit = kg / kg; // units of mass mixing ratios of tracers + constexpr auto n_unit = 1 / kg; // units of number mixing ratios of tracers - auto nondim = ekat::units::Units::nondimensional(); + constexpr auto nondim = ekat::units::Units::nondimensional(); // atmospheric quantities // specific humidity [kg/kg] @@ -101,28 +103,28 @@ void MAMAci::set_grids( add_tracer("ni", grid_, n_unit); // Temperature[K] at midpoints - add_field("T_mid", scalar3d_layout_mid, K, grid_name); + add_field("T_mid", scalar3d_mid, K, grid_name); // Vertical pressure velocity [Pa/s] at midpoints - add_field("omega", scalar3d_layout_mid, Pa / s, grid_name); + add_field("omega", scalar3d_mid, Pa / s, grid_name); // Total pressure [Pa] at midpoints - add_field("p_mid", scalar3d_layout_mid, Pa, grid_name); + add_field("p_mid", scalar3d_mid, Pa, grid_name); // Total pressure [Pa] at interfaces - add_field("p_int", scalar3d_layout_int, Pa, grid_name); + add_field("p_int", scalar3d_int, Pa, grid_name); // Layer thickness(pdel) [Pa] at midpoints - add_field("pseudo_density", scalar3d_layout_mid, Pa, grid_name); + add_field("pseudo_density", scalar3d_mid, Pa, grid_name); // planetary boundary layer height add_field("pbl_height", scalar2d_layout_col, m, grid_name); // cloud fraction [nondimensional] computed by eamxx_cld_fraction_process - add_field("cldfrac_tot", scalar3d_layout_mid, nondim, grid_name); + add_field("cldfrac_tot", scalar3d_mid, nondim, grid_name); - auto m2 = pow(m, 2); - auto s2 = pow(s, 2); + constexpr auto m2 = pow(m, 2); + constexpr auto s2 = pow(s, 2); // NOTE: w_variance im microp_aero.F90 in EAM is at "itim_old" dynamics time // step. Since, we are using SE dycore, itim_old is 1 which is equivalent to @@ -130,29 +132,30 @@ void MAMAci::set_grids( // and we might need to revisit this. // Vertical velocity variance at midpoints - add_field("w_variance", scalar3d_layout_mid, m2 / s2, grid_name); + add_field("w_variance", scalar3d_mid, m2 / s2, grid_name); // NOTE: "cldfrac_liq" is updated in SHOC. "cldfrac_liq" in C++ code is // equivalent to "alst" in the shoc_intr.F90. In the C++ code, it is used as // "shoc_cldfrac" and in the F90 code it is called "cloud_frac" // Liquid stratiform cloud fraction at midpoints - add_field("cldfrac_liq", scalar3d_layout_mid, nondim, grid_name); + add_field("cldfrac_liq", scalar3d_mid, nondim, grid_name); // Previous value of liquid stratiform cloud fraction at midpoints - add_field("cldfrac_liq_prev", scalar3d_layout_mid, nondim, - grid_name); + add_field("cldfrac_liq_prev", scalar3d_mid, nondim, grid_name); // Eddy diffusivity for heat - add_field("eddy_diff_heat", scalar3d_layout_mid, m2 / s, grid_name); + add_field("eddy_diff_heat", scalar3d_mid, m2 / s, grid_name); - // Layout for 4D (2d horiz X 1d vertical x number of modes) variables - const int num_aero_modes = mam_coupling::num_aero_modes(); - FieldLayout scalar4d_layout_mid = - make_layout({ncol_, num_aero_modes, nlev_}, {"COL", "NMODES", "LEV"}); + // Number of modes + constexpr int nmodes = mam4::AeroConfig::num_modes(); + + // layout for 3D (ncol, nmodes, nlevs) + FieldLayout scalar3d_mid_nmodes = + grid_->get_3d_vector_layout(true, nmodes, "nmodes"); // dry diameter of aerosols [m] - add_field("dgnum", scalar4d_layout_mid, m, grid_name); + add_field("dgnum", scalar3d_mid_nmodes, m, grid_name); // ======================================================================== // Output from this whole process @@ -171,8 +174,7 @@ void MAMAci::set_grids( // NOT advected const char *cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(mode); - add_field(cld_nmr_field_name, scalar3d_layout_mid, n_unit, - grid_name); + add_field(cld_nmr_field_name, scalar3d_mid, n_unit, grid_name); for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { // (interstitial) aerosol tracers of interest: mass (q) mixing ratios @@ -187,8 +189,7 @@ void MAMAci::set_grids( const char *cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(mode, a); if(strlen(cld_mmr_field_name) > 0) { - add_field(cld_mmr_field_name, scalar3d_layout_mid, q_unit, - grid_name); + add_field(cld_mmr_field_name, scalar3d_mid, q_unit, grid_name); } } // end for loop num species } // end for loop for num modes @@ -203,54 +204,52 @@ void MAMAci::set_grids( // ------------------------------------------------------------------------ // number of activated aerosol for ice nucleation[#/kg] - add_field("ni_activated", scalar3d_layout_mid, n_unit, grid_name); + add_field("ni_activated", scalar3d_mid, n_unit, grid_name); // ------------------------------------------------------------------------ // Output from droplet activation process (dropmixnuc) // ------------------------------------------------------------------------ // tendency in droplet number mixing ratio [#/kg/s] - add_field("nc_nuceat_tend", scalar3d_layout_mid, n_unit / s, - grid_name); + add_field("nc_nuceat_tend", scalar3d_mid, n_unit / s, grid_name); // FIXME: [TEMPORARY]droplet number mixing ratio source tendency [#/kg/s] - add_field("nsource", scalar3d_layout_mid, n_unit / s, grid_name); + add_field("nsource", scalar3d_mid, n_unit / s, grid_name); // FIXME: [TEMPORARY]droplet number mixing ratio tendency due to mixing // [#/kg/s] - add_field("ndropmix", scalar3d_layout_mid, n_unit / s, grid_name); + add_field("ndropmix", scalar3d_mid, n_unit / s, grid_name); // FIXME: [TEMPORARY]droplet number as seen by ACI [#/kg] - add_field("nc_inp_to_aci", scalar3d_layout_mid, n_unit / s, - grid_name); - const auto cm_tmp = m / 100; // FIXME: [TEMPORARY] remove this - const auto cm3 = cm_tmp * cm_tmp * cm_tmp; // FIXME: [TEMPORARY] remove this + add_field("nc_inp_to_aci", scalar3d_mid, n_unit / s, grid_name); + constexpr auto cm_tmp = m / 100; // FIXME: [TEMPORARY] remove this + constexpr auto cm3 = pow(cm_tmp, 3); // FIXME: [TEMPORARY] remove this // FIXME: [TEMPORARY] remove the following ccn outputs - add_field("ccn_0p02", scalar3d_layout_mid, cm3, grid_name); - add_field("ccn_0p05", scalar3d_layout_mid, cm3, grid_name); - add_field("ccn_0p1", scalar3d_layout_mid, cm3, grid_name); - add_field("ccn_0p2", scalar3d_layout_mid, cm3, grid_name); - add_field("ccn_0p5", scalar3d_layout_mid, cm3, grid_name); - add_field("ccn_1p0", scalar3d_layout_mid, cm3, grid_name); + add_field("ccn_0p02", scalar3d_mid, cm3, grid_name); + add_field("ccn_0p05", scalar3d_mid, cm3, grid_name); + add_field("ccn_0p1", scalar3d_mid, cm3, grid_name); + add_field("ccn_0p2", scalar3d_mid, cm3, grid_name); + add_field("ccn_0p5", scalar3d_mid, cm3, grid_name); + add_field("ccn_1p0", scalar3d_mid, cm3, grid_name); // ------------------------------------------------------------------------ // Output from hetrozenous freezing // ------------------------------------------------------------------------ - const auto cm = m / 100; + constexpr auto cm = m / 100; // units of number mixing ratios of tracers - auto frz_unit = 1 / (cm * cm * cm * s); + constexpr auto frz_unit = 1 / (cm * cm * cm * s); // heterogeneous freezing by immersion nucleation [cm^-3 s^-1] - add_field("hetfrz_immersion_nucleation_tend", scalar3d_layout_mid, + add_field("hetfrz_immersion_nucleation_tend", scalar3d_mid, frz_unit, grid_name); // heterogeneous freezing by contact nucleation [cm^-3 s^-1] - add_field("hetfrz_contact_nucleation_tend", scalar3d_layout_mid, - frz_unit, grid_name); + add_field("hetfrz_contact_nucleation_tend", scalar3d_mid, frz_unit, + grid_name); // heterogeneous freezing by deposition nucleation [cm^-3 s^-1] - add_field("hetfrz_deposition_nucleation_tend", scalar3d_layout_mid, + add_field("hetfrz_deposition_nucleation_tend", scalar3d_mid, frz_unit, grid_name); } // function set_grids ends @@ -299,11 +298,11 @@ void MAMAci::initialize_impl(const RunType run_type) { // store rest fo the atm fields in dry_atm_in dry_atm_.z_surf = 0; - dry_atm_.T_mid = get_field_in("T_mid").get_view(); - dry_atm_.p_mid = get_field_in("p_mid").get_view(); - dry_atm_.p_int = get_field_in("p_int").get_view(); - dry_atm_.p_del = get_field_in("pseudo_density").get_view(); - dry_atm_.omega = get_field_in("omega").get_view(); + dry_atm_.T_mid = get_field_in("T_mid").get_view(); + dry_atm_.p_mid = get_field_in("p_mid").get_view(); + dry_atm_.p_int = get_field_in("p_int").get_view(); + dry_atm_.p_del = get_field_in("pseudo_density").get_view(); + dry_atm_.omega = get_field_in("omega").get_view(); // store fields converted to dry mmr from wet mmr in dry_atm_ dry_atm_.qv = buffer_.qv_dry; @@ -561,7 +560,8 @@ void MAMAci::run_impl(const double dt) { // output w0_, rho_); - compute_tke_at_interfaces(team_policy, w_sec_mid_, dry_atm_.dz, nlev_, w_sec_int_, + compute_tke_at_interfaces(team_policy, w_sec_mid_, dry_atm_.dz, nlev_, + w_sec_int_, // output tke_); @@ -594,25 +594,26 @@ void MAMAci::run_impl(const double dt) { // output cloud_frac_, cloud_frac_prev_); - mam_coupling::compute_recipical_pseudo_density(team_policy, dry_atm_.p_del, nlev_, - // output - rpdel_); + mam_coupling::compute_recipical_pseudo_density(team_policy, dry_atm_.p_del, + nlev_, + // output + rpdel_); Kokkos::fence(); // wait for rpdel_ to be computed. // Compute activated CCN number tendency (tendnd_) and updated // cloud borne aerosols (stored in a work array) and interstitial // aerosols tendencies - call_function_dropmixnuc(team_policy, dt, dry_atm_, rpdel_, kvh_mid_, kvh_int_, wsub_, - cloud_frac_, cloud_frac_prev_, dry_aero_, nlev_, - // output - coltend_, coltend_cw_, qcld_, ndropcol_, ndropmix_, - nsource_, wtke_, ccn_, - // ## output to be used by the other processes ## - qqcw_fld_work_, ptend_q_, factnum_, tendnd_, - // work arrays - raercol_cw_, raercol_, state_q_work_, nact_, mact_, - dropmixnuc_scratch_mem_); + call_function_dropmixnuc( + team_policy, dt, dry_atm_, rpdel_, kvh_mid_, kvh_int_, wsub_, cloud_frac_, + cloud_frac_prev_, dry_aero_, nlev_, + // output + coltend_, coltend_cw_, qcld_, ndropcol_, ndropmix_, nsource_, wtke_, ccn_, + // ## output to be used by the other processes ## + qqcw_fld_work_, ptend_q_, factnum_, tendnd_, + // work arrays + raercol_cw_, raercol_, state_q_work_, nact_, mact_, + dropmixnuc_scratch_mem_); Kokkos::fence(); // wait for ptend_q_ to be computed. Kokkos::deep_copy(ccn_0p02_, diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 2ca410611fe..bf5678113d4 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -1,37 +1,19 @@ -#include // for serial NetCDF file reads on MPI root - -#include -#include #include -#include -#include -#include - -#include "scream_config.h" // for SCREAM_CIME_BUILD -#include "share/util/scream_common_physics_functions.hpp" -// NOTE: see the impl/ directory for the contents of the impl namespace -#include "impl/compute_o3_column_density.cpp" -#include "impl/gas_phase_chemistry.cpp" +// impl namespace for some driver level functions for microphysics +#include "impl/photo_table_utils.cpp" #include "physics/rrtmgp/shr_orb_mod_c2f.hpp" -// For EKAT units package -#include "ekat/util/ekat_units.hpp" - -// For EKAT subview -#include -#include - -/* When the preprocessor definition ENABLE_OUTPUT_TRACER_FIELDS is -enabled, the fields oxi_fields, linoz_fields, and -vertical_emission_fields will be saved. -These fields, which are the output of the tracer reader, - have been stored in the FM for evaluation purposes. - There are 33 fields (ncolxnlev) in total. - Therefore, it is recommended to disable - the ENABLE_OUTPUT_TRACER_FIELDS preprocessor - definition once the evaluation of the microphysics interface - is completed. */ +// When the preprocessor definition ENABLE_OUTPUT_TRACER_FIELDS is +// enabled, the fields oxi_fields, linoz_fields, and +// vertical_emission_fields will be saved. +// These fields, which are the output of the tracer reader, +// have been stored in the FM for evaluation purposes. +// There are 33 fields (ncolxnlev) in total. +// Therefore, it is recommended to disable +// the ENABLE_OUTPUT_TRACER_FIELDS preprocessor +// definition once the evaluation of the microphysics interface +// is completed. #define ENABLE_OUTPUT_TRACER_FIELDS namespace scream { @@ -39,35 +21,50 @@ namespace scream { MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, const ekat::ParameterList ¶ms) : AtmosphereProcess(comm, params), aero_config_() { - configure(params); -} + // Asserts for the runtime or namelist options + // ----- Aerosol Microphysics processes on/off switches ------- + EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_cond"), + "ERROR: mam4_do_cond is missing from mam4_aero_microphys " + "parameter list."); -AtmosphereProcessType MAMMicrophysics::type() const { - return AtmosphereProcessType::Physics; -} + EKAT_REQUIRE_MSG( + m_params.isParameter("mam4_do_rename"), + "ERROR: mam4_do_rename is missing from mam4_aero_microphys " + "parameter list."); -std::string MAMMicrophysics::name() const { return "mam4_micro"; } + EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_newnuc"), + "ERROR: mam4_do_newnuc is missing from mam4_aero_microphys " + " parameter list."); -namespace { + EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_coag"), + "ERROR: mam4_do_coag is missing from mam4_aero_microphys " + "parameter list."); + // ----- LINOZ namelist parameters ------- -void set_data_file(const char *name, const char *path, - char location[MAX_FILENAME_LEN]) { - EKAT_REQUIRE_MSG(strlen(SCREAM_DATA_DIR) + strlen(path) < MAX_FILENAME_LEN, - "Error! " << name << " path is too long (must be < " - << MAX_FILENAME_LEN << " characters)"); - sprintf(location, "%s/%s", SCREAM_DATA_DIR, path); -} + EKAT_REQUIRE_MSG( + m_params.isParameter("mam4_o3_tau"), + "ERROR: mam4_o3_tau is missing from mam4_aero_microphys parameter list."); -} // namespace + EKAT_REQUIRE_MSG(m_params.isParameter("mam4_o3_sfc"), + "ERROR: mam4_o3_sfc is missing from mam4_aero_microphys " + "parameter list."); -#define set_file_location(data_file, path) \ - set_data_file(#data_file, path, data_file) + EKAT_REQUIRE_MSG(m_params.isParameter("mam4_o3_lbl"), + "ERROR: mam4_o3_lbl is missing from mam4_aero_microphys " + "parameter list."); + + set_namelist_params_(); +} -void MAMMicrophysics::set_defaults_() { - config_.amicphys.do_cond = true; - config_.amicphys.do_rename = true; - config_.amicphys.do_newnuc = true; - config_.amicphys.do_coag = true; +AtmosphereProcessType MAMMicrophysics::type() const { + return AtmosphereProcessType::Physics; +} + +void MAMMicrophysics::set_namelist_params_() { + config_.amicphys.do_cond = m_params.get("mam4_do_cond"); + config_.amicphys.do_rename = m_params.get("mam4_do_rename"); + config_.amicphys.do_newnuc = m_params.get("mam4_do_newnuc"); + config_.amicphys.do_coag = m_params.get("mam4_do_coag"); // these parameters guide the coupling between parameterizations // NOTE: mam4xx was ported with these parameters fixed, so it's probably not @@ -75,41 +72,19 @@ void MAMMicrophysics::set_defaults_() { config_.amicphys.gaexch_h2so4_uptake_optaa = 2; config_.amicphys.newnuc_h2so4_conc_optaa = 2; - //=========================================================== - // default data file locations (relative to SCREAM_DATA_DIR) - //=========================================================== - - // many of these paths were extracted from - // e3smv2/bld/namelist_files/namelist_defaults_eam.xml - - // photolysis - // set_file_location(config_.photolysis.rsf_file, - // "../waccm/phot/RSF_GT200nm_v3.0_c080811.nc"); - // set_file_location(config_.photolysis.xs_long_file, - // "../waccm/phot/temp_prs_GT200nm_JPL10_c130206.nc"); - - // stratospheric chemistry - // set_file_location(config_.linoz.chlorine_loading_file, - // "../cam/chem/trop_mozart/ub/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"); - - // dry deposition - set_file_location(config_.drydep.srf_file, - "../cam/chem/trop_mam/atmsrf_ne4pg2_200527.nc"); -} - -void MAMMicrophysics::configure(const ekat::ParameterList ¶ms) { - set_defaults_(); + // LINOZ namelist parameters + o3_lbl_ = m_params.get("mam4_o3_lbl"); + o3_tau_ = m_params.get("mam4_o3_tau"); + o3_sfc_ = m_params.get("mam4_o3_sfc"); } +// ================================================================ +// SET_GRIDS +// ================================================================ void MAMMicrophysics::set_grids( const std::shared_ptr grids_manager) { - using namespace ekat::units; - - Units nondim = Units::nondimensional(); - Units n_unit(1 / kg, "#/kg"); // number mixing ratios [# / kg air] - const auto m2 = pow(m, 2); - const auto s2 = pow(s, 2); - + // set grid for all the inputs and outputs + // use physics grid grid_ = grids_manager->get_grid("Physics"); const auto &grid_name = grid_->name(); @@ -117,120 +92,143 @@ void MAMMicrophysics::set_grids( nlev_ = grid_->get_num_vertical_levels(); // number of levels per column // get column geometry and locations - col_areas_ = grid_->get_geometry_data("area").get_view(); - col_latitudes_ = grid_->get_geometry_data("lat").get_view(); - col_longitudes_ = grid_->get_geometry_data("lon").get_view(); + col_latitudes_ = grid_->get_geometry_data("lat").get_view(); // define the different field layouts that will be used for this process using namespace ShortFieldTagsNames; - // layout for 2D (1d horiz X 1d vertical) variable - FieldLayout scalar2d_layout_col{{COL}, {ncol_}}; + // Layout for 2D (2d horiz) variable + const FieldLayout scalar2d = grid_->get_2d_scalar_layout(); + + // Layout for 3D (2d horiz X 1d vertical) variable defined at mid-level and + // interfaces + const FieldLayout scalar3d_mid = grid_->get_3d_scalar_layout(true); + const FieldLayout scalar3d_int = grid_->get_3d_scalar_layout(false); - // layout for 3D (2d horiz X 1d vertical) variables - FieldLayout scalar3d_layout_mid{{COL, LEV}, {ncol_, nlev_}}; - // At interfaces - FieldLayout scalar3d_layout_int{{COL, ILEV}, {ncol_, nlev_ + 1}}; + using namespace ekat::units; + constexpr auto q_unit = kg / kg; // units of mass mixing ratios of tracers + constexpr auto n_unit = 1 / kg; // units of number mixing ratios of tracers + + // -------------------------------------------------------------------------- + // These variables are "Required" or pure inputs for the process + // -------------------------------------------------------------------------- + + // ----------- Atmospheric quantities ------------- + + // Specific humidity [kg/kg](Require only for building DS) + add_field("qv", scalar3d_mid, q_unit, grid_name, "tracers"); + + // Cloud liquid mass mixing ratio [kg/kg](Require only for building DS) + add_field("qc", scalar3d_mid, q_unit, grid_name, "tracers"); + + // Cloud ice mass mixing ratio [kg/kg](Require only for building DS) + add_field("qi", scalar3d_mid, q_unit, grid_name, "tracers"); + + // Cloud liquid number mixing ratio [1/kg](Require only for building DS) + add_field("nc", scalar3d_mid, n_unit, grid_name, "tracers"); + + // Cloud ice number mixing ratio [1/kg](Require only for building DS) + add_field("ni", scalar3d_mid, n_unit, grid_name, "tracers"); + + // Temperature[K] at midpoints + add_field("T_mid", scalar3d_mid, K, grid_name); + + // Vertical pressure velocity [Pa/s] at midpoints (Require only for building + // DS) + add_field("omega", scalar3d_mid, Pa / s, grid_name); + + // Total pressure [Pa] at midpoints + add_field("p_mid", scalar3d_mid, Pa, grid_name); + + // Total pressure [Pa] at interfaces + add_field("p_int", scalar3d_int, Pa, grid_name); + + // Layer thickness(pdel) [Pa] at midpoints + add_field("pseudo_density", scalar3d_mid, Pa, grid_name); + + // Planetary boundary layer height [m] + add_field("pbl_height", scalar2d, m, grid_name); + + constexpr auto m2 = pow(m, 2); + constexpr auto s2 = pow(s, 2); + + // Surface geopotential [m2/s2] + add_field("phis", scalar2d, m2 / s2, grid_name); + + //----------- Variables from microphysics scheme ------------- + constexpr auto nondim = ekat::units::Units::nondimensional(); + // Total cloud fraction [fraction] + add_field("cldfrac_liq", scalar3d_mid, nondim, grid_name); + + //----------- Variables from other mam4xx processes ------------ + // Number of modes + constexpr int nmodes = mam4::AeroConfig::num_modes(); - const int nmodes = mam4::AeroConfig::num_modes(); // Number of modes // layout for 3D (ncol, nmodes, nlevs) FieldLayout scalar3d_mid_nmodes = grid_->get_3d_vector_layout(true, nmodes, "nmodes"); - static constexpr auto m3 = m * m * m; - // Aerosol dry particle diameter [m] - add_field("dgncur_a", scalar3d_mid_nmodes, m, grid_name); + // Geometric mean dry diameter for number distribution [m] + add_field("dgnum", scalar3d_mid_nmodes, m, grid_name); + // Geometric mean wet diameter for number distribution [m] + add_field("dgnumwet", scalar3d_mid_nmodes, m, grid_name); - // Wet aerosol density [kg/m3] + constexpr auto m3 = pow(m, 3); + // Wet density of interstitial aerosol [kg/m3] add_field("wetdens", scalar3d_mid_nmodes, kg / m3, grid_name); - // Wet Required diameter [m] - add_field("dgnumwet", scalar3d_mid_nmodes, m, grid_name); + //----------- Variables from coupler (land component)--------- + // surface albedo shortwave, direct + add_field("sfc_alb_dir_vis", scalar2d, nondim, grid_name); - // define fields needed in mam4xx + // --------------------------------------------------------------------- + // These variables are "updated" or inputs/outputs for the process + // --------------------------------------------------------------------- - // atmospheric quantities - add_field("omega", scalar3d_layout_mid, Pa / s, - grid_name); // vertical pressure velocity - add_field("T_mid", scalar3d_layout_mid, K, - grid_name); // Temperature - add_field("p_mid", scalar3d_layout_mid, Pa, - grid_name); // total pressure - // Total pressure [Pa] at interfaces - add_field("p_int", scalar3d_layout_int, Pa, grid_name); - add_field("qv", scalar3d_layout_mid, kg / kg, grid_name, - "tracers"); // specific humidity - add_field("qi", scalar3d_layout_mid, kg / kg, grid_name, - "tracers"); // ice wet mixing ratio - add_field("ni", scalar3d_layout_mid, n_unit, grid_name, - "tracers"); // ice number mixing ratio - add_field("pbl_height", scalar2d_layout_col, m, - grid_name); // planetary boundary layer height - add_field("pseudo_density", scalar3d_layout_mid, Pa, - grid_name); // p_del, hydrostatic pressure - add_field("phis", scalar2d_layout_col, m2 / s2, grid_name); - add_field("cldfrac_tot", scalar3d_layout_mid, nondim, - grid_name); // cloud fraction - add_field("sfc_alb_dir_vis", scalar2d_layout_col, nondim, - grid_name); // surface albedo shortwave, direct - - // droplet activation can alter cloud liquid and number mixing ratios - add_field("qc", scalar3d_layout_mid, kg / kg, grid_name, - "tracers"); // cloud liquid wet mixing ratio - add_field("nc", scalar3d_layout_mid, n_unit, grid_name, - "tracers"); // cloud liquid wet number mixing ratio - - // interstitial and cloudborne aerosol tracers of interest: mass (q) and - // number (n) mixing ratios - for(int m = 0; m < mam_coupling::num_aero_modes(); ++m) { - // interstitial aerosol tracers of interest: number (n) mixing ratios + // (interstitial) aerosol tracers of interest: mass (q) and number (n) mixing + // ratios + for(int m = 0; m < nmodes; ++m) { const char *int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); - add_field(int_nmr_field_name, scalar3d_layout_mid, n_unit, - grid_name, "tracers"); - - // cloudborne aerosol tracers of interest: number (n) mixing ratios - // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are - // NOT advected - const char *cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); - add_field(cld_nmr_field_name, scalar3d_layout_mid, n_unit, - grid_name); + add_field(int_nmr_field_name, scalar3d_mid, n_unit, grid_name, + "tracers"); for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { - // (interstitial) aerosol tracers of interest: mass (q) mixing ratios const char *int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); + if(strlen(int_mmr_field_name) > 0) { - add_field(int_mmr_field_name, scalar3d_layout_mid, kg / kg, - grid_name, "tracers"); + add_field(int_mmr_field_name, scalar3d_mid, q_unit, grid_name, + "tracers"); } + } // for loop species + } // for loop nmodes interstitial + // (cloud) aerosol tracers of interest: mass (q) and number (n) mixing ratios + for(int m = 0; m < nmodes; ++m) { + const char *cld_nmr_field_name = mam_coupling::cld_aero_nmr_field_name(m); - // (cloudborne) aerosol tracers of interest: mass (q) mixing ratios - // NOTE: DO NOT add cld borne aerosols to the "tracer" group as these are - // NOT advected + add_field(cld_nmr_field_name, scalar3d_mid, n_unit, grid_name); + for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { const char *cld_mmr_field_name = mam_coupling::cld_aero_mmr_field_name(m, a); + if(strlen(cld_mmr_field_name) > 0) { - add_field(cld_mmr_field_name, scalar3d_layout_mid, kg / kg, - grid_name); + add_field(cld_mmr_field_name, scalar3d_mid, q_unit, grid_name); } - } // end for loop for num species - } // end for loop for num modes + } // for loop species + } // for loop nmodes cld borne // aerosol-related gases: mass mixing ratios for(int g = 0; g < mam_coupling::num_aero_gases(); ++g) { const char *gas_mmr_field_name = mam_coupling::gas_mmr_field_name(g); - add_field(gas_mmr_field_name, scalar3d_layout_mid, kg / kg, - grid_name, "tracers"); + add_field(gas_mmr_field_name, scalar3d_mid, q_unit, grid_name, + "tracers"); } - // Tracers group -- do we need this in addition to the tracers above? In any - // case, this call should be idempotent, so it can't hurt. - add_group("tracers", grid_name, 1, Bundling::Required); // Creating a Linoz reader and setting Linoz parameters involves reading data // from a file and configuring the necessary parameters for the Linoz model. { linoz_file_name_ = m_params.get("mam4_linoz_file_name"); - std::string spa_map_file = ""; + std::string linoz_map_file = ""; std::vector var_names{"o3_clim", "o3col_clim", "t_clim", "PmL_clim", "dPmL_dO3", "dPmL_dT", "dPmL_dO3col", "cariolle_pscs"}; @@ -240,7 +238,7 @@ void MAMMicrophysics::set_grids( scream::mam_coupling::setup_tracer_data(linoz_data_, linoz_file_name_, linoz_cyclical_ymd); LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, linoz_file_name_, spa_map_file, var_names, linoz_data_); + grid_, linoz_file_name_, linoz_map_file, var_names, linoz_data_); LinozDataReader_ = scream::mam_coupling::create_tracer_data_reader( LinozHorizInterp_, linoz_file_name_); @@ -269,11 +267,11 @@ void MAMMicrophysics::set_grids( linoz_cariolle_psc Cariolle parameter for PSC loss of ozone [1/s] */ add_field("linoz_fields", scalar3d_mid_linoz, nondim, grid_name); #endif - } + } // LINOZ reader { - oxid_file_name_ = m_params.get("mam4_oxid_file_name"); - std::string spa_map_file = ""; + oxid_file_name_ = m_params.get("mam4_oxid_file_name"); + std::string oxid_map_file = ""; // NOTE: order matches mam4xx: std::vector var_names{"O3", "OH", "NO3", "HO2"}; @@ -282,7 +280,7 @@ void MAMMicrophysics::set_grids( scream::mam_coupling::setup_tracer_data(tracer_data_, oxid_file_name_, oxid_ymd); TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( - grid_, oxid_file_name_, spa_map_file, var_names, tracer_data_); + grid_, oxid_file_name_, oxid_map_file, var_names, tracer_data_); TracerDataReader_ = scream::mam_coupling::create_tracer_data_reader( TracerHorizInterp_, oxid_file_name_); @@ -305,11 +303,11 @@ void MAMMicrophysics::set_grids( // NOTE: Assuming nondim for units. add_field("oxi_fields", scalar3d_mid_oxi, nondim, grid_name); #endif - } + } // oxid file reader { // FIXME: I will need to add this file per forcing file. - std::string spa_map_file = ""; + std::string extfrc_map_file = ""; // NOTE: order of forcing species is important. // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 // ','pom_a4 ','bc_a4 ', 'num_a1 ','num_a2 @@ -350,7 +348,7 @@ void MAMMicrophysics::set_grids( scream::mam_coupling::setup_tracer_data(data_tracer, file_name, verti_emiss_cyclical_ymd); auto hor_rem = scream::mam_coupling::create_horiz_remapper( - grid_, file_name, spa_map_file, var_names, data_tracer); + grid_, file_name, extfrc_map_file, var_names, data_tracer); auto file_reader = scream::mam_coupling::create_tracer_data_reader(hor_rem, file_name); VertEmissionsHorizInterp_.push_back(hor_rem); @@ -398,35 +396,27 @@ void MAMMicrophysics::set_grids( add_field("vertical_emission_fields", scalar3d_mid_emis_ver, nondim, grid_name); #endif - } -} - -// this checks whether we have the tracers we expect -void MAMMicrophysics::set_computed_group_impl(const FieldGroup &group) { - const auto &name = group.m_info->m_group_name; - EKAT_REQUIRE_MSG( - name == "tracers", - "Error! MAM4 expects a 'tracers' field group (got '" << name << "')\n"); - - EKAT_REQUIRE_MSG(group.m_info->m_bundled, - "Error! MAM4 expects bundled fields for tracers.\n"); - - // how many aerosol/gas tracers do we expect? - int num_tracers = mam_coupling::num_aero_modes() + - mam_coupling::num_aero_tracers() + - mam_coupling::num_aero_gases(); - EKAT_REQUIRE_MSG(group.m_info->size() >= num_tracers, - "Error! MAM4 requires at least " - << group.m_info->size() << " " << num_tracers << " " - << mam_coupling::num_aero_modes() << " " - << mam_coupling::num_aero_tracers() << " " - << mam_coupling::num_aero_gases() << "aerosol tracers."); -} + } // Tracer external forcing data +} // set_grids +// ================================================================ +// REQUEST_BUFFER_SIZE_IN_BYTES +// ================================================================ +// ON HOST, returns the number of bytes of device memory needed by +// the above. Buffer type given the number of columns and vertical +// levels size_t MAMMicrophysics::requested_buffer_size_in_bytes() const { return mam_coupling::buffer_size(ncol_, nlev_); } +// ================================================================ +// INIT_BUFFERS +// ================================================================ +// ON HOST, initializeÑ• the Buffer type with sufficient memory to +// store intermediate (dry) quantities on the given number of +// columns with the given number of vertical levels. Returns the +// number of bytes allocated. + void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { EKAT_REQUIRE_MSG( buffer_manager.allocated_bytes() >= requested_buffer_size_in_bytes(), @@ -439,9 +429,10 @@ void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { "Error! Used memory != requested memory for MAMMicrophysics."); } +// ================================================================ +// INITIALIZE_IMPL +// ================================================================ void MAMMicrophysics::initialize_impl(const RunType run_type) { - step_ = 0; - // Determine orbital year. If orbital_year is negative, use current year // from timestamp for orbital year; if positive, use provided orbital year // for duration of simulation. @@ -452,20 +443,24 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { m_orbital_obliq = m_params.get("orbital_obliquity", -9999); m_orbital_mvelp = m_params.get("orbital_mvelp", -9999); - // populate the wet and dry atmosphere states with views from fields and - // the buffer + // --------------------------------------------------------------- + // Input fields read in from IC file, namelist or other processes + // --------------------------------------------------------------- + + // Populate the wet atmosphere state with views from fields + // FIMXE: specifically look which among these are actually used by the process + wet_atm_.qv = get_field_in("qv").get_view(); - wet_atm_.qc = get_field_out("qc").get_view(); - wet_atm_.nc = get_field_out("nc").get_view(); + wet_atm_.qc = get_field_in("qc").get_view(); + wet_atm_.nc = get_field_in("nc").get_view(); wet_atm_.qi = get_field_in("qi").get_view(); wet_atm_.ni = get_field_in("ni").get_view(); - dry_atm_.T_mid = get_field_in("T_mid").get_view(); - dry_atm_.p_mid = get_field_in("p_mid").get_view(); - dry_atm_.p_int = get_field_in("p_int").get_view(); - dry_atm_.p_del = get_field_in("pseudo_density").get_view(); - dry_atm_.cldfrac = get_field_in("cldfrac_tot") - .get_view(); // FIXME: tot or liq? + dry_atm_.T_mid = get_field_in("T_mid").get_view(); + dry_atm_.p_mid = get_field_in("p_mid").get_view(); + dry_atm_.p_int = get_field_in("p_int").get_view(); + dry_atm_.p_del = get_field_in("pseudo_density").get_view(); + dry_atm_.cldfrac = get_field_in("cldfrac_liq").get_view(); dry_atm_.pblh = get_field_in("pbl_height").get_view(); dry_atm_.phis = get_field_in("phis").get_view(); dry_atm_.omega = get_field_in("omega").get_view(); @@ -483,10 +478,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // get surface albedo: shortwave, direct d_sfc_alb_dir_vis_ = get_field_in("sfc_alb_dir_vis").get_view(); - // perform any initialization work - if(run_type == RunType::Initial) { - } - // interstitial and cloudborne aerosol tracers of interest: mass (q) and // number (n) mixing ratios for(int m = 0; m < mam_coupling::num_aero_modes(); ++m) { @@ -520,8 +511,8 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { get_field_out(cld_mmr_field_name).get_view(); dry_aero_.cld_aero_mmr[m][a] = buffer_.dry_cld_aero_mmr[m][a]; } - } - } + } // for loop species + } // for loop num_aero_modes() // set wet/dry aerosol-related gas state data for(int g = 0; g < mam_coupling::num_aero_gases(); ++g) { @@ -536,11 +527,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { m_params.get("mam4_xs_long_file"); photo_table_ = impl::read_photo_table(rsf_file, xs_long_file); - // set up our preprocess/postprocess functors - preprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, - dry_aero_); - postprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, - dry_aero_); // set field property checks for the fields in this process /* e.g. @@ -552,14 +538,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { add_postcondition_check(get_field_out("tke"),m_grid,0); */ - // set up WSM for internal local variables - // (we'll probably need this later, but we'll just use ATMBufferManager for - // now) - // const auto default_policy = - // ekat::ExeSpaceUtils::get_default_team_policy(ncol_, nlev_); - // workspace_mgr_.setup(buffer_.wsm_data, nlev_+1, - // 13+(n_wind_slots+n_trac_slots), default_policy); - { // climatology data for linear stratospheric chemistry auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] @@ -578,9 +556,6 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { auto linoz_cariolle_pscs = buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] - // const std::string linoz_chlorine_file = - // "Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc"; auto ts = - // timestamp(); int chlorine_loading_ymd=20100101; auto ts = timestamp(); std::string linoz_chlorine_file = m_params.get("mam4_linoz_chlorine_file"); @@ -588,7 +563,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { scream::mam_coupling::create_linoz_chlorine_reader( linoz_chlorine_file, ts, chlorine_loading_ymd, chlorine_values_, chlorine_time_secs_); - } + } // LINOZ const int photo_table_len = get_photo_table_work_len(photo_table_); work_photo_table_ = view_2d("work_photo_table", ncol_, photo_table_len); @@ -596,10 +571,9 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // here's where we store per-column photolysis rates photo_rates_ = view_3d("photo_rates", ncol_, nlev_, mam4::mo_photo::phtcnt); - // - // Load the first month into spa_end. - // Note: At the first time step, the data will be moved into spa_beg, - // and spa_end will be reloaded from file with the new month. + // Load the first month into extfrc_lst_end. + // Note: At the first time step, the data will be moved into extfrc_lst_beg, + // and extfrc_lst_end will be reloaded from file with the new month. const int curr_month = timestamp().get_month() - 1; // 0-based scream::mam_coupling::update_tracer_data_from_file( @@ -623,12 +597,20 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // acos_cosine_zenith_host_ = view_1d_host("host_acos(cosine_zenith)", ncol_); acos_cosine_zenith_ = view_1d("device_acos(cosine_zenith)", ncol_); -} + + //----------------------------------------------------------------- + // Setup preprocessing and post processing + //----------------------------------------------------------------- + preprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, + dry_aero_); + postprocess_.initialize(ncol_, nlev_, wet_atm_, wet_aero_, dry_atm_, + dry_aero_); + +} // initialize_impl // ================================================================ // RUN_IMPL // ================================================================ - void MAMMicrophysics::run_impl(const double dt) { const auto scan_policy = ekat::ExeSpaceUtils< KT::ExeSpace>::get_thread_range_parallel_scan_team_policy(ncol_, nlev_); @@ -642,24 +624,24 @@ void MAMMicrophysics::run_impl(const double dt) { const auto wet_geometric_mean_diameter_i = get_field_in("dgnumwet").get_view(); const auto dry_geometric_mean_diameter_i = - get_field_in("dgncur_a").get_view(); + get_field_in("dgnum").get_view(); const auto wetdens = get_field_in("wetdens").get_view(); // climatology data for linear stratospheric chemistry - auto linoz_o3_clim = buffer_.scratch[0]; // ozone (climatology) [vmr] - auto linoz_o3col_clim = - buffer_ - .scratch[1]; // column o3 above box (climatology) [Dobson Units (DU)] - auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] + // ozone (climatology) [vmr] + auto linoz_o3_clim = buffer_.scratch[0]; + // column o3 above box (climatology) [Dobson Units (DU)] + auto linoz_o3col_clim = buffer_.scratch[1]; + auto linoz_t_clim = buffer_.scratch[2]; // temperature (climatology) [K] auto linoz_PmL_clim = buffer_.scratch[3]; // P minus L (climatology) [vmr/s] - auto linoz_dPmL_dO3 = - buffer_.scratch[4]; // sensitivity of P minus L to O3 [1/s] - auto linoz_dPmL_dT = - buffer_.scratch[5]; // sensitivity of P minus L to T3 [K] - auto linoz_dPmL_dO3col = buffer_.scratch[6]; // sensitivity of P minus L to - // overhead O3 column [vmr/DU] - auto linoz_cariolle_pscs = - buffer_.scratch[7]; // Cariolle parameter for PSC loss of ozone [1/s] + // sensitivity of P minus L to O3 [1/s] + auto linoz_dPmL_dO3 = buffer_.scratch[4]; + // sensitivity of P minus L to T3 [K] + auto linoz_dPmL_dT = buffer_.scratch[5]; + // sensitivity of P minus L to overhead O3 column [vmr/DU] + auto linoz_dPmL_dO3col = buffer_.scratch[6]; + // Cariolle parameter for PSC loss of ozone [1/s] + auto linoz_cariolle_pscs = buffer_.scratch[7]; view_2d linoz_output[8]; linoz_output[0] = linoz_o3_clim; @@ -684,8 +666,12 @@ void MAMMicrophysics::run_impl(const double dt) { // addition of dt */ trace_time_state_.t_now = ts.frac_of_year_in_days(); scream::mam_coupling::advance_tracer_data( - TracerDataReader_, *TracerHorizInterp_, ts, trace_time_state_, - tracer_data_, dry_atm_.p_mid, dry_atm_.z_iface, cnst_offline_); + TracerDataReader_, // in + *TracerHorizInterp_, // out + ts, // in + trace_time_state_, tracer_data_, // out + dry_atm_.p_mid, dry_atm_.z_iface, // in + cnst_offline_); // out Kokkos::fence(); #if defined(ENABLE_OUTPUT_TRACER_FIELDS) @@ -699,8 +685,12 @@ void MAMMicrophysics::run_impl(const double dt) { #endif scream::mam_coupling::advance_tracer_data( - LinozDataReader_, *LinozHorizInterp_, ts, linoz_time_state_, linoz_data_, - dry_atm_.p_mid, dry_atm_.z_iface, linoz_output); + LinozDataReader_, // in + *LinozHorizInterp_, // out + ts, // in + linoz_time_state_, linoz_data_, // out + dry_atm_.p_mid, dry_atm_.z_iface, // in + linoz_output); // out Kokkos::fence(); #if defined(ENABLE_OUTPUT_TRACER_FIELDS) @@ -742,8 +732,7 @@ void MAMMicrophysics::run_impl(const double dt) { } #endif - const_view_1d &col_latitudes = col_latitudes_; - // const_view_1d &col_longitudes = col_longitudes_; + const_view_1d &col_latitudes = col_latitudes_; const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; mam_coupling::DryAtmosphere &dry_atm = dry_atm_; @@ -753,7 +742,6 @@ void MAMMicrophysics::run_impl(const double dt) { mam4::mo_photo::PhotoTableData &photo_table = photo_table_; const int nlev = nlev_; const Config &config = config_; - const auto &step = step_; const auto &work_photo_table = work_photo_table_; const auto &photo_rates = photo_rates_; @@ -785,8 +773,8 @@ void MAMMicrophysics::run_impl(const double dt) { // Use the orbital parameters to calculate the solar declination and // eccentricity factor Real delta, eccf; - auto calday = ts2.frac_of_year_in_days() + - 1; // Want day + fraction; calday 1 == Jan 1 0Z + // Want day + fraction; calday 1 == Jan 1 0Z + auto calday = ts2.frac_of_year_in_days() + 1; shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, obliqr, // in &delta, &eccf); // out { @@ -797,8 +785,7 @@ void MAMMicrophysics::run_impl(const double dt) { // get a host copy of lat/lon // Determine the cosine zenith angle // NOTE: Since we are bridging to F90 arrays this must be done on HOST and - // then - // deep copied to a device view. + // then deep copied to a device view. // Now use solar declination to calculate zenith angle for all points for(int i = 0; i < ncol_; i++) { @@ -836,44 +823,39 @@ void MAMMicrophysics::run_impl(const double dt) { clsmap_4[i] = mam4::gas_chemistry::clsmap_4[i]; permute_4[i] = mam4::gas_chemistry::permute_4[i]; } + // LINOZ parameters from the namelist + const int o3_lbl = o3_lbl_; + const Real o3_tau = o3_tau_; + const Real o3_sfc = o3_sfc_; // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for( policy, KOKKOS_LAMBDA(const ThreadTeam &team) { - const int icol = team.league_rank(); // column index - - Real col_lat = col_latitudes(icol); // column latitude (degrees?) - // Real col_lon = col_longitudes(icol); // column longitude + const int icol = team.league_rank(); // column index + const Real col_lat = col_latitudes(icol); // column latitude (degrees?) - Real rlats = - col_lat * M_PI / 180.0; // convert column latitude to radians - // Real rlons = - // col_lon * M_PI / 180.0; // convert column longitude to radians + // convert column latitude to radians + const Real rlats = col_lat * M_PI / 180.0; // fetch column-specific atmosphere state data - auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); - auto z_iface = ekat::subview(dry_atm.z_iface, icol); - Real phis = dry_atm.phis(icol); - - auto wet_diameter_icol = + const auto atm = mam_coupling::atmosphere_for_column(dry_atm, icol); + const auto wet_diameter_icol = ekat::subview(wet_geometric_mean_diameter_i, icol); - auto dry_diameter_icol = + const auto dry_diameter_icol = ekat::subview(dry_geometric_mean_diameter_i, icol); - auto wetdens_icol = ekat::subview(wetdens, icol); + const auto wetdens_icol = ekat::subview(wetdens, icol); // fetch column-specific subviews into aerosol prognostics mam4::Prognostics progs = mam_coupling::aerosols_for_column(dry_aero, icol); - // set up diagnostics - mam4::Diagnostics diags(nlev); const auto invariants_icol = ekat::subview(invariants, icol); mam4::mo_setext::Forcing forcings_in[extcnt]; for(int i = 0; i < extcnt; ++i) { - int nsectors = forcings[i].nsectors; - int frc_ndx = forcings[i].frc_ndx; - auto file_alt_data = forcings[i].file_alt_data; + const int nsectors = forcings[i].nsectors; + const int frc_ndx = forcings[i].frc_ndx; + const auto file_alt_data = forcings[i].file_alt_data; forcings_in[i].nsectors = nsectors; forcings_in[i].frc_ndx = frc_ndx; @@ -887,202 +869,43 @@ void MAMMicrophysics::run_impl(const double dt) { const auto extfrc_icol = ekat::subview(extfrc, icol); - mam4::mo_setext::extfrc_set(forcings_in, extfrc_icol); - view_1d cnst_offline_icol[mam4::mo_setinv::num_tracer_cnst]; for(int i = 0; i < mam4::mo_setinv::num_tracer_cnst; ++i) { cnst_offline_icol[i] = ekat::subview(cnst_offline[i], icol); } - mam4::mo_setinv::setinv(team, invariants_icol, atm.temperature, - atm.vapor_mixing_ratio, cnst_offline_icol, - atm.pressure); - // calculate o3 column densities (first component of col_dens in Fortran // code) auto o3_col_dens_i = ekat::subview(o3_col_dens, icol); - impl::compute_o3_column_density(team, atm, progs, invariants_icol, - adv_mass_kg_per_moles, o3_col_dens_i); - - // set up photolysis work arrays for this column. - mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays_icol; const auto &work_photo_table_icol = ekat::subview(work_photo_table, icol); - // set work view using 1D photo_work_arrays_icol - // Note: We are not allocating views here. - mam4::mo_photo::set_photo_table_work_arrays( - photo_table, work_photo_table_icol, photo_work_arrays_icol); - const auto &photo_rates_icol = ekat::subview(photo_rates, icol); - mam4::mo_photo::table_photo( - photo_rates_icol, atm.pressure, atm.hydrostatic_dp, atm.temperature, - o3_col_dens_i, zenith_angle(icol), d_sfc_alb_dir_vis(icol), - atm.liquid_mixing_ratio, atm.cloud_fraction, eccf, photo_table, - photo_work_arrays_icol); - - // compute aerosol microphysics on each vertical level within this - // column - Kokkos::parallel_for( - Kokkos::TeamThreadRange(team, nlev), [&](const int kk) { - // extract atm state variables (input) - Real temp = atm.temperature(kk); - Real pmid = atm.pressure(kk); - Real pdel = atm.hydrostatic_dp(kk); - Real zm = atm.height(kk); - Real zi = z_iface(kk); - Real pblh = atm.planetary_boundary_layer_height; - Real qv = atm.vapor_mixing_ratio(kk); - Real cldfrac = atm.cloud_fraction(kk); - Real lwc = atm.liquid_mixing_ratio(kk); - Real cldnum = atm.cloud_liquid_number_mixing_ratio(kk); - - // extract aerosol state variables into "working arrays" (mass - // mixing ratios) (in EAM, this is done in the gas_phase_chemdr - // subroutine defined within - // mozart/mo_gas_phase_chemdr.F90) - Real state_q[pcnst] = {}; - Real qqcw_pcnst[pcnst] = {}; - // output (state_q) - mam4::utils::extract_stateq_from_prognostics(progs, atm, state_q, - kk); - // output (qqcw_pcnst) - mam4::utils::extract_qqcw_from_prognostics(progs, qqcw_pcnst, kk); - - Real qq[gas_pcnst] = {}; - Real qqcw[gas_pcnst] = {}; - for(int i = offset_aerosol; i < pcnst; ++i) { - qq[i - offset_aerosol] = state_q[i]; - qqcw[i - offset_aerosol] = qqcw_pcnst[i]; - } - // convert mass mixing ratios to volume mixing ratios (VMR), - // equivalent to tracer mixing ratios (TMR)) - Real vmr[gas_pcnst], vmrcw[gas_pcnst]; - // output (vmr) - mam_coupling::mmr2vmr(qq, adv_mass_kg_per_moles, vmr); - // output (vmrcw) - mam_coupling::mmr2vmr(qqcw, adv_mass_kg_per_moles, vmrcw); - - //--------------------- - // Gas Phase Chemistry - //--------------------- - // - const auto &extfrc_k = ekat::subview(extfrc_icol, kk); - const auto &invariants_k = ekat::subview(invariants_icol, kk); - const auto &photo_rates_k = ekat::subview(photo_rates_icol, kk); - - // vmr0 stores mixing ratios before chemistry changes the mixing - // ratios - Real vmr0[gas_pcnst] = {}; - impl::gas_phase_chemistry( - // in - temp, dt, photo_rates_k.data(), extfrc_k.data(), - invariants_k.data(), clsmap_4, permute_4, - // out - vmr, vmr0); - - // create work array copies to retain "pre-chemistry (aqueous)" - // values - Real vmr_pregas[gas_pcnst] = {}; - Real vmr_precld[gas_pcnst] = {}; - for(int i = 0; i < gas_pcnst; ++i) { - vmr_pregas[i] = vmr[i]; - vmr_precld[i] = vmrcw[i]; - } - - //---------------------- - // Aerosol microphysics - //---------------------- - // the logic below is taken from the aero_model_gasaerexch - // subroutine in eam/src/chemistry/modal_aero/aero_model.F90 - - // aqueous chemistry ... - const Real mbar = haero::Constants::molec_weight_dry_air; - constexpr int indexm = mam4::gas_chemistry::indexm; - mam4::mo_setsox::setsox_single_level( - // in - offset_aerosol, dt, pmid, pdel, temp, mbar, lwc, cldfrac, - cldnum, invariants_k[indexm], config.setsox, - // out - vmrcw, vmr); - - // calculate aerosol water content using water uptake treatment - // * dry and wet diameters [m] - // * wet densities [kg/m3] - // * aerosol water mass mixing ratio [kg/kg] - Real dgncur_a_kk[num_modes] = {}; - Real dgncur_awet_kk[num_modes] = {}; - Real wetdens_kk[num_modes] = {}; - Real qaerwat_kk[num_modes] = {}; - - for(int imode = 0; imode < num_modes; imode++) { - dgncur_awet_kk[imode] = wet_diameter_icol(imode, kk); - dgncur_a_kk[imode] = dry_diameter_icol(imode, kk); - wetdens_kk[imode] = wetdens_icol(imode, kk); - } - // do aerosol microphysics (gas-aerosol exchange, nucleation, - // coagulation) - // FIXME: Verify cldfrac is the right one by looking at EAM - // variable - impl::modal_aero_amicphys_intr( - // in - config.amicphys, dt, temp, pmid, pdel, zm, pblh, qv, cldfrac, - // out - vmr, vmrcw, - // in - vmr0, vmr_pregas, vmr_precld, dgncur_a_kk, dgncur_awet_kk, - wetdens_kk); - - //----------------- - // LINOZ chemistry - //----------------- - - // the following things are diagnostics, which we're not - // including in the first rev - Real do3_linoz, do3_linoz_psc, ss_o3, o3col_du_diag, - o3clim_linoz_diag, zenith_angle_degrees; - - int o3_ndx = 0; // index of "O3" in solsym array (in EAM) - mam4::lin_strat_chem::lin_strat_chem_solve_kk( - o3_col_dens_i(kk), temp, zenith_angle(icol), pmid, dt, rlats, - linoz_o3_clim(icol, kk), linoz_t_clim(icol, kk), - linoz_o3col_clim(icol, kk), linoz_PmL_clim(icol, kk), - linoz_dPmL_dO3(icol, kk), linoz_dPmL_dT(icol, kk), - linoz_dPmL_dO3col(icol, kk), linoz_cariolle_pscs(icol, kk), - chlorine_loading, config.linoz.psc_T, vmr[o3_ndx], do3_linoz, - do3_linoz_psc, ss_o3, o3col_du_diag, o3clim_linoz_diag, - zenith_angle_degrees); - - // update source terms above the ozone decay threshold - /*if (kk > nlev - config.linoz.o3_lbl - 1) { - Real do3_mass; // diagnostic, not needed - mam4::lin_strat_chem::lin_strat_sfcsink_kk(dt, pdel, - vmr[o3_ndx], config.linoz.o3_sfc, config.linoz.o3_tau, do3_mass); - }*/ - - // ... check for negative values and reset to zero - for(int i = 0; i < gas_pcnst; ++i) { - if(vmr[i] < 0.0) vmr[i] = 0.0; - } - - //---------------------- - // Dry deposition (gas) - //---------------------- - - // FIXME: C++ port in progress! - // mam4::drydep::drydep_xactive(...); - - mam_coupling::vmr2mmr(vmr, adv_mass_kg_per_moles, qq); - mam_coupling::vmr2mmr(vmrcw, adv_mass_kg_per_moles, qqcw); - - for(int i = offset_aerosol; i < pcnst; ++i) { - state_q[i] = qq[i - offset_aerosol]; - qqcw_pcnst[i] = qqcw[i - offset_aerosol]; - } - mam4::utils::inject_stateq_to_prognostics(state_q, progs, kk); - mam4::utils::inject_qqcw_to_prognostics(qqcw_pcnst, progs, kk); - }); // parallel_for for vertical levels - }); // parallel_for for the column loop + + const auto linoz_o3_clim_icol = ekat::subview(linoz_o3_clim, icol); + const auto linoz_t_clim_icol = ekat::subview(linoz_t_clim, icol); + const auto linoz_o3col_clim_icol = + ekat::subview(linoz_o3col_clim, icol); + const auto linoz_PmL_clim_icol = ekat::subview(linoz_PmL_clim, icol); + const auto linoz_dPmL_dO3_icol = ekat::subview(linoz_dPmL_dO3, icol); + const auto linoz_dPmL_dT_icol = ekat::subview(linoz_dPmL_dT, icol); + const auto linoz_dPmL_dO3col_icol = + ekat::subview(linoz_dPmL_dO3col, icol); + const auto linoz_cariolle_pscs_icol = + ekat::subview(linoz_cariolle_pscs, icol); + // Note: All variables are inputs, except for progs, which is an input/output variable. + mam4::microphysics::perform_atmospheric_chemistry_and_microphysics( + team, dt, rlats, cnst_offline_icol, forcings_in, atm, progs, + photo_table, chlorine_loading, config.setsox, config.amicphys, + config.linoz.psc_T, zenith_angle(icol), d_sfc_alb_dir_vis(icol), + o3_col_dens_i, photo_rates_icol, extfrc_icol, invariants_icol, + work_photo_table_icol, linoz_o3_clim_icol, linoz_t_clim_icol, + linoz_o3col_clim_icol, linoz_PmL_clim_icol, linoz_dPmL_dO3_icol, + linoz_dPmL_dT_icol, linoz_dPmL_dO3col_icol, + linoz_cariolle_pscs_icol, eccf, adv_mass_kg_per_moles, clsmap_4, + permute_4, offset_aerosol, o3_sfc, o3_tau, o3_lbl, + dry_diameter_icol, wet_diameter_icol, wetdens_icol); + }); // parallel_for for the column loop Kokkos::fence(); // postprocess output @@ -1091,6 +914,4 @@ void MAMMicrophysics::run_impl(const double dt) { } // MAMMicrophysics::run_impl -void MAMMicrophysics::finalize_impl() {} - } // namespace scream diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 87fca4d5fff..dbd97057123 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -5,28 +5,14 @@ #include #include -#include "impl/mam4_amicphys.cpp" // mam4xx top-level microphysics function(s) +// For reading files #include "impl/helper_micro.hpp" -#include -#include +// For calling MAM4 processes #include -#include "share/io/scorpio_input.hpp" -#include "share/io/scream_scorpio_interface.hpp" - - #include -#ifndef KOKKOS_ENABLE_CUDA -#define protected_except_cuda public -#define private_except_cuda public -#else -#define protected_except_cuda protected -#define private_except_cuda private -#endif - -namespace scream -{ +namespace scream { // The process responsible for handling MAM4 aerosol microphysics. The AD // stores exactly ONE instance of this class in its list of subcomponents. @@ -35,34 +21,19 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { using KT = ekat::KokkosTypes; // views for single- and multi-column data - using view_1d_int = typename KT::template view_1d; using view_1d = typename KT::template view_1d; using view_2d = typename KT::template view_2d; using view_3d = typename KT::template view_3d; using const_view_1d = typename KT::template view_1d; - using const_view_2d = typename KT::template view_2d; using view_1d_host = typename KT::view_1d::HostMirror; - // unmanaged views (for buffer and workspace manager) - using uview_1d = Unmanaged>; - using uview_2d = Unmanaged>; - - // a quantity stored in a single vertical column with a single index - using ColumnView = mam4::ColumnView; - // a thread team dispatched to a single vertical column using ThreadTeam = mam4::ThreadTeam; - using TracerFileType = mam_coupling::TracerFileType; - - -public: - + public: // Constructor - MAMMicrophysics(const ekat::Comm& comm, const ekat::ParameterList& params); - -protected_except_cuda: + MAMMicrophysics(const ekat::Comm &comm, const ekat::ParameterList ¶ms); // -------------------------------------------------------------------------- // AtmosphereProcess overrides (see share/atm_process/atmosphere_process.hpp) @@ -70,31 +41,35 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // process metadata AtmosphereProcessType type() const override; - std::string name() const override; - // set aerosol microphysics configuration parameters (called by constructor) - void configure(const ekat::ParameterList& params); + // The name of the subcomponent + std::string name() const { return "mam_aero_microphysics"; } // grid - void set_grids(const std::shared_ptr grids_manager) override; + void set_grids( + const std::shared_ptr grids_manager) override; // management of common atm process memory size_t requested_buffer_size_in_bytes() const override; void init_buffers(const ATMBufferManager &buffer_manager) override; - // process behavior + // Initialize variables void initialize_impl(const RunType run_type) override; - void run_impl(const double dt) override; - void finalize_impl() override; - // performs some checks on the tracers group - void set_computed_group_impl(const FieldGroup& group) override; + // Run the process by one time step + void run_impl(const double dt) override; -private_except_cuda: + // Finalize + void finalize_impl(){/*Do nothing*/}; + private: // number of horizontal columns and vertical levels int ncol_, nlev_; + // Namelist for LINOZ + int o3_lbl_; + Real o3_tau_, o3_sfc_; + // The orbital year, used for zenith angle calculations: // If > 0, use constant orbital year for duration of simulation // If < 0, use year from timestamp for orbital parameters @@ -119,10 +94,10 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // stratospheric chemistry parameters struct { - int o3_lbl; // number of layers with ozone decay from the surface - int o3_sfc; // set from namelist input linoz_sfc - int o3_tau; // set from namelist input linoz_tau - Real psc_T; // set from namelist input linoz_psc_T + int o3_lbl; // number of layers with ozone decay from the surface + int o3_sfc; // set from namelist input linoz_sfc + int o3_tau; // set from namelist input linoz_tau + Real psc_T; // set from namelist input linoz_psc_T char chlorine_loading_file[MAX_FILENAME_LEN]; } linoz; @@ -130,7 +105,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam4::mo_setsox::Config setsox; // aero microphysics configuration (see impl/mam4_amicphys.cpp) - impl::AmicPhysConfig amicphys; + mam4::microphysics::AmicPhysConfig amicphys; // dry deposition parameters struct { @@ -147,39 +122,41 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // on host: initializes preprocess functor with necessary state data void initialize(const int ncol, const int nlev, - const mam_coupling::WetAtmosphere& wet_atm, - const mam_coupling::AerosolState& wet_aero, - const mam_coupling::DryAtmosphere& dry_atm, - const mam_coupling::AerosolState& dry_aero) { - ncol_ = ncol; - nlev_ = nlev; - wet_atm_ = wet_atm; - wet_aero_ = wet_aero; - dry_atm_ = dry_atm; - dry_aero_ = dry_aero; + const mam_coupling::WetAtmosphere &wet_atm, + const mam_coupling::AerosolState &wet_aero, + const mam_coupling::DryAtmosphere &dry_atm, + const mam_coupling::AerosolState &dry_aero) { + ncol_pre_ = ncol; + nlev_pre_ = nlev; + wet_atm_pre_ = wet_atm; + wet_aero_pre_ = wet_aero; + dry_atm_pre_ = dry_atm; + dry_aero_pre_ = dry_aero; } KOKKOS_INLINE_FUNCTION - void operator()(const Kokkos::TeamPolicy::member_type& team) const { - const int i = team.league_rank(); // column index + void operator()( + const Kokkos::TeamPolicy::member_type &team) const { + const int i = team.league_rank(); // column index - compute_dry_mixing_ratios(team, wet_atm_, dry_atm_, i); - compute_dry_mixing_ratios(team, wet_atm_, wet_aero_, dry_aero_, i); + compute_dry_mixing_ratios(team, wet_atm_pre_, dry_atm_pre_, i); + compute_dry_mixing_ratios(team, wet_atm_pre_, wet_aero_pre_, + dry_aero_pre_, i); team.team_barrier(); - compute_vertical_layer_heights(team, dry_atm_, i); - compute_updraft_velocities(team, wet_atm_, dry_atm_, i); - } // operator() + compute_vertical_layer_heights(team, dry_atm_pre_, i); + compute_updraft_velocities(team, wet_atm_pre_, dry_atm_pre_, i); + } // operator() // number of horizontal columns and vertical levels - int ncol_, nlev_; + int ncol_pre_, nlev_pre_; // local atmospheric and aerosol state data - mam_coupling::WetAtmosphere wet_atm_; - mam_coupling::DryAtmosphere dry_atm_; - mam_coupling::AerosolState wet_aero_, dry_aero_; + mam_coupling::WetAtmosphere wet_atm_pre_; + mam_coupling::DryAtmosphere dry_atm_pre_; + mam_coupling::AerosolState wet_aero_pre_, dry_aero_pre_; - }; // MAMMicrophysics::Preprocess + }; // MAMMicrophysics::Preprocess // Postprocessing functor struct Postprocess { @@ -187,33 +164,35 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // on host: initializes postprocess functor with necessary state data void initialize(const int ncol, const int nlev, - const mam_coupling::WetAtmosphere& wet_atm, - const mam_coupling::AerosolState& wet_aero, - const mam_coupling::DryAtmosphere& dry_atm, - const mam_coupling::AerosolState& dry_aero) { - ncol_ = ncol; - nlev_ = nlev; - wet_atm_ = wet_atm; - wet_aero_ = wet_aero; - dry_atm_ = dry_atm; - dry_aero_ = dry_aero; + const mam_coupling::WetAtmosphere &wet_atm, + const mam_coupling::AerosolState &wet_aero, + const mam_coupling::DryAtmosphere &dry_atm, + const mam_coupling::AerosolState &dry_aero) { + ncol_post_ = ncol; + nlev_post_ = nlev; + wet_atm_post_ = wet_atm; + wet_aero_post_ = wet_aero; + dry_atm_post_ = dry_atm; + dry_aero_post_ = dry_aero; } KOKKOS_INLINE_FUNCTION - void operator()(const Kokkos::TeamPolicy::member_type& team) const { - const int i = team.league_rank(); // column index - compute_wet_mixing_ratios(team, dry_atm_, dry_aero_, wet_aero_, i); + void operator()( + const Kokkos::TeamPolicy::member_type &team) const { + const int i = team.league_rank(); // column index + compute_wet_mixing_ratios(team, dry_atm_post_, dry_aero_post_, + wet_aero_post_, i); team.team_barrier(); - } // operator() + } // operator() // number of horizontal columns and vertical levels - int ncol_, nlev_; + int ncol_post_, nlev_post_; // local atmospheric and aerosol state data - mam_coupling::WetAtmosphere wet_atm_; - mam_coupling::DryAtmosphere dry_atm_; - mam_coupling::AerosolState wet_aero_, dry_aero_; - }; // MAMMicrophysics::Postprocess + mam_coupling::WetAtmosphere wet_atm_post_; + mam_coupling::DryAtmosphere dry_atm_post_; + mam_coupling::AerosolState wet_aero_post_, dry_aero_post_; + }; // MAMMicrophysics::Postprocess // MAM4 aerosol particle size description mam4::AeroConfig aero_config_; @@ -225,29 +204,26 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // atmospheric and aerosol state variables mam_coupling::WetAtmosphere wet_atm_; mam_coupling::DryAtmosphere dry_atm_; - mam_coupling::AerosolState wet_aero_, dry_aero_; + mam_coupling::AerosolState wet_aero_, dry_aero_; // photolysis rate table (column-independent) mam4::mo_photo::PhotoTableData photo_table_; // column areas, latitudes, longitudes - const_view_1d col_areas_, col_latitudes_, col_longitudes_; + const_view_1d col_latitudes_, col_longitudes_; // surface albedo: shortwave, direct const_view_1d d_sfc_alb_dir_vis_; - // time step number - int step_; - // workspace manager for internal local variables - //ekat::WorkspaceManager workspace_mgr_; + // ekat::WorkspaceManager workspace_mgr_; mam_coupling::Buffer buffer_; // physics grid for column information std::shared_ptr grid_; // sets defaults for "namelist parameters" - void set_defaults_(); + void set_namelist_params_(); mam_coupling::TracerTimeState linoz_time_state_; view_2d work_photo_table_; @@ -257,7 +233,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // invariants members mam_coupling::TracerTimeState trace_time_state_; - std::shared_ptr TracerDataReader_; + std::shared_ptr TracerDataReader_; std::shared_ptr TracerHorizInterp_; mam_coupling::TracerData tracer_data_; view_3d invariants_; @@ -265,19 +241,20 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_2d cnst_offline_[4]; // linoz reader - std::shared_ptr LinozDataReader_; + std::shared_ptr LinozDataReader_; std::shared_ptr LinozHorizInterp_; mam_coupling::TracerData linoz_data_; std::string linoz_file_name_; - // Vertical emission uses 9 files, here I am using std::vector to stote instance of each file. + // Vertical emission uses 9 files, here I am using std::vector to stote + // instance of each file. mam_coupling::TracerTimeState vert_emiss_time_state_; - std::vector> VertEmissionsDataReader_; + std::vector> VertEmissionsDataReader_; std::vector> VertEmissionsHorizInterp_; std::vector extfrc_lst_; std::vector vert_emis_data_; - std::map< std::string, std::string >vert_emis_file_name_; - std::map< std::string, std::vector > vert_emis_var_names_; + std::map vert_emis_file_name_; + std::map> vert_emis_var_names_; view_2d vert_emis_output_[mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS]; view_3d extfrc_; mam_coupling::ForcingHelper forcings_[mam4::gas_chemistry::extcnt]; @@ -285,8 +262,8 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { view_1d_host acos_cosine_zenith_host_; view_1d acos_cosine_zenith_; -}; // MAMMicrophysics +}; // MAMMicrophysics -} // namespace scream +} // namespace scream -#endif // EAMXX_MAM_MICROPHYSICS_HPP +#endif // EAMXX_MAM_MICROPHYSICS_HPP diff --git a/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp index 32148d580fe..f0374a5617d 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp @@ -205,7 +205,7 @@ void MAMWetscav::set_grids( static constexpr auto m3 = m * m * m; // Aerosol dry particle diameter [m] - add_field("dgncur_a", scalar3d_mid_nmodes, m, grid_name); + add_field("dgnum", scalar3d_mid_nmodes, m, grid_name); // Wet aerosol density [kg/m3] add_field("wetdens", scalar3d_mid_nmodes, kg / m3, grid_name); @@ -475,7 +475,7 @@ void MAMWetscav::run_impl(const double dt) { const auto wet_geometric_mean_diameter_i = get_field_out("dgnumwet").get_view(); const auto dry_geometric_mean_diameter_i = - get_field_out("dgncur_a").get_view(); + get_field_out("dgnum").get_view(); const auto qaerwat = get_field_out("qaerwat").get_view(); const auto wetdens = get_field_out("wetdens").get_view(); diff --git a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp b/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp deleted file mode 100644 index d3c4e172b58..00000000000 --- a/components/eamxx/src/physics/mam/impl/compute_o3_column_density.cpp +++ /dev/null @@ -1,51 +0,0 @@ -namespace scream::impl { - -using View2D = DeviceType::view_2d; - -KOKKOS_INLINE_FUNCTION -void compute_o3_column_density( - const ThreadTeam &team, const haero::Atmosphere &atm, - const mam4::Prognostics &progs, const View2D &invariants, - const Real adv_mass_kg_per_moles[mam4::gas_chemistry::gas_pcnst], - ColumnView o3_col_dens) { - constexpr int gas_pcnst = - mam4::gas_chemistry::gas_pcnst; // number of gas phase species - constexpr int nfs = mam4::gas_chemistry::nfs; // number of "fixed species" - constexpr int offset_aerosol = mam4::utils::gasses_start_ind(); - - Real o3_col_deltas[mam4::nlev + 1] = - {}; // o3 column density above model [1/cm^2] - // NOTE: if we need o2 column densities, set_ub_col and setcol must be changed - Kokkos::parallel_for( - Kokkos::TeamThreadRange(team, mam4::nlev), [&](const int k) { - Real pdel = atm.hydrostatic_dp(k); - // extract aerosol state variables into "working arrays" (mass - // mixing ratios) (in EAM, this is done in the gas_phase_chemdr - // subroutine defined within - // mozart/mo_gas_phase_chemdr.F90) - Real q[gas_pcnst] = {}; - Real state_q[pcnst] = {}; - mam4::utils::extract_stateq_from_prognostics(progs, atm, state_q, k); - - for(int i = offset_aerosol; i < pcnst; ++i) { - q[i - offset_aerosol] = state_q[i]; - } - - // convert mass mixing ratios to volume mixing ratios (VMR), - // equivalent to tracer mixing ratios (TMR)) - Real vmr[gas_pcnst]; - mam_coupling::mmr2vmr(q, adv_mass_kg_per_moles, vmr); - // ... compute invariants for this level - Real invariants_k[nfs]; - for(int i = 0; i < nfs; ++i) { - invariants_k[i] = invariants(k, i); - } - // compute the change in o3 density for this column above its neighbor - mam4::mo_photo::set_ub_col(o3_col_deltas[k + 1], vmr, invariants_k, - pdel); - }); - // sum the o3 column deltas to densities - mam4::mo_photo::setcol(o3_col_deltas, o3_col_dens); -} - -} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/helper_micro.hpp index be989971541..c8e1c0cc831 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/helper_micro.hpp @@ -395,15 +395,19 @@ inline std::shared_ptr create_horiz_remapper( horiz_interp_tgt_grid, IdentityRemapper::SrcAliasTgt); } else { EKAT_REQUIRE_MSG(tracer_data.ncols_data <= ncols_model, - "Error! We do not allow to coarsen spa data to fit the " + "Error! We do not allow to coarsen tracer external " + "forcing data to fit the " "model. We only allow\n" - " spa data to be at the same or coarser resolution " + " tracer external forcing data to be at the same or " + "coarser resolution " "as the model.\n"); // We must have a valid map file EKAT_REQUIRE_MSG( map_file != "", - "ERROR: Spa data is on a different grid than the model one,\n" - " but spa_remap_file is missing from SPA parameter list."); + "ERROR: tracer external forcing data is on a different grid than the " + "model one,\n" + " but tracer external forcing data remap file is missing from " + "tracer external forcing data parameter list."); remapper = std::make_shared(horiz_interp_tgt_grid, map_file); @@ -447,13 +451,13 @@ inline std::shared_ptr create_tracer_data_reader( } // create_tracer_data_reader inline void update_tracer_data_from_file( - std::shared_ptr &scorpio_reader, + const std::shared_ptr &scorpio_reader, const int time_index, // zero-based AbstractRemapper &tracer_horiz_interp, TracerData &tracer_data) { // 1. read from field scorpio_reader->read_variables(time_index); - // 2. Run the horiz remapper (it is a do-nothing op if spa data is on same - // grid as model) + // 2. Run the horiz remapper (it is a do-nothing op if tracer external forcing + // data is on same grid as model) tracer_horiz_interp.remap(/*forward = */ true); // const int nvars = tracer_data.nvars_; @@ -472,11 +476,12 @@ inline void update_tracer_data_from_file( } // update_tracer_data_from_file inline void update_tracer_timestate( - std::shared_ptr &scorpio_reader, const util::TimeStamp &ts, - AbstractRemapper &tracer_horiz_interp, TracerTimeState &time_state, - TracerData &data_tracer) { + const std::shared_ptr &scorpio_reader, + const util::TimeStamp &ts, AbstractRemapper &tracer_horiz_interp, + TracerTimeState &time_state, TracerData &data_tracer) { // Now we check if we have to update the data that changes monthly - // NOTE: This means that SPA assumes monthly data to update. Not + // NOTE: This means that tracer external forcing assumes monthly data to + // update. Not // any other frequency. const auto month = ts.get_month() - 1; // Make it 0-based if(month != time_state.current_month) { @@ -484,7 +489,7 @@ inline void update_tracer_timestate( const int nvars = data_tracer.nvars_; const auto ps = data_tracer.ps; - // Update the SPA time state information + // Update the tracer external forcing time state information time_state.current_month = month; time_state.t_beg_month = util::TimeStamp({ts.get_year(), month + 1, 1}, {0, 0, 0}) @@ -505,7 +510,7 @@ inline void update_tracer_timestate( // Assume the data is saved monthly and cycles in one year // Add offset_time_index to support cases where data is saved // from other periods of time. - // Update the SPA forcing data for this month and next month + // Update the tracer external forcing data for this month and next month // Start by copying next months data to this months data structure. // NOTE: If the timestep is bigger than monthly this could cause the wrong // values @@ -685,11 +690,12 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, } inline void advance_tracer_data( - std::shared_ptr &scorpio_reader, - AbstractRemapper &tracer_horiz_interp, const util::TimeStamp &ts, - TracerTimeState &time_state, TracerData &data_tracer, - const const_view_2d &p_tgt, const const_view_2d &zi_tgt, - const view_2d output[]) { + const std::shared_ptr &scorpio_reader, // in + AbstractRemapper &tracer_horiz_interp, // out + const util::TimeStamp &ts, // in + TracerTimeState &time_state, TracerData &data_tracer, // out + const const_view_2d &p_tgt, const const_view_2d &zi_tgt, // in + const view_2d output[]) { // out /* Update the TracerTimeState to reflect the current time, note the addition * of dt */ time_state.t_now = ts.frac_of_year_in_days(); diff --git a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp b/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp deleted file mode 100644 index 0f7c5138ae5..00000000000 --- a/components/eamxx/src/physics/mam/impl/mam4_amicphys.cpp +++ /dev/null @@ -1,2355 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace scream::impl { - -using namespace mam4; - -// number of constituents in gas chemistry "work arrays" -using mam4::gas_chemistry::gas_pcnst; - -// number of modes in modal configuration -constexpr int num_modes = AeroConfig::num_modes(); - -// number of gases -constexpr int num_gas_ids = AeroConfig::num_gas_ids(); - -// number of aerosol species -constexpr int num_aerosol_ids = AeroConfig::num_aerosol_ids(); - -//----------------------------------------------------------------------------- -// Indices for amicphys -//----------------------------------------------------------------------------- - -// Indices of aerosol number for the arrays dimensioned gas_pcnst -KOKKOS_INLINE_FUNCTION int numptr_amode_gas_pcnst(const int mode) { - static constexpr int numptr_amode_gas_pcnst_[num_modes] = {13, 18, 26, 30}; - return numptr_amode_gas_pcnst_[mode]; -} - -// Indices of aerosol mass for the arrays dimensioned gas_pcnst -KOKKOS_INLINE_FUNCTION int lmassptr_amode_gas_pcnst(const int aero_id, - const int mode) { - static constexpr int lmassptr_amode_gas_pcnst_[num_aerosol_ids][num_modes] = { - {6, 14, 19, 27}, {7, 15, 20, 28}, {8, 16, 21, 29}, {9, 17, 22, -6}, - {10, -6, 23, -6}, {11, -6, 24, -6}, {12, -6, 25, -6}}; - return lmassptr_amode_gas_pcnst_[aero_id][mode]; -} -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_nul() { return 0; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_gas() { return 1; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_aer() { return 2; } -KOKKOS_INLINE_FUNCTION constexpr int lmapcc_val_num() { return 3; } -KOKKOS_INLINE_FUNCTION int lmapcc_all(const int index) { - static constexpr int lmapcc_all_[gas_pcnst] = { - lmapcc_val_nul(), lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_nul(), - lmapcc_val_nul(), lmapcc_val_gas(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num(), lmapcc_val_aer(), - lmapcc_val_aer(), lmapcc_val_aer(), lmapcc_val_num()}; - return lmapcc_all_[index]; -} - -// Where lmapcc_val_gas are defined in lmapcc_all -KOKKOS_INLINE_FUNCTION int lmap_gas(const int mode) { - static constexpr int lmap_gas_[num_modes] = {5, 2}; - return lmap_gas_[mode]; -} - -// number of aerosol/gas species tendencies -KOKKOS_INLINE_FUNCTION -constexpr int nqtendbb() { return 4; } - -KOKKOS_INLINE_FUNCTION constexpr int nqtendaa() { return 4; } -KOKKOS_INLINE_FUNCTION constexpr int nqqcwtendaa() { return 1; } -KOKKOS_INLINE_FUNCTION constexpr int iqtend_cond() { return 0; } -KOKKOS_INLINE_FUNCTION constexpr int iqtend_rnam() { return 1; } -KOKKOS_INLINE_FUNCTION constexpr int iqtend_nnuc() { return 2; } -KOKKOS_INLINE_FUNCTION constexpr int iqtend_coag() { return 3; } -KOKKOS_INLINE_FUNCTION constexpr int iqtend_cond_only() { return 4; } -KOKKOS_INLINE_FUNCTION constexpr int iqqcwtend_rnam() { return 0; } -KOKKOS_INLINE_FUNCTION constexpr int n_agepair() { return 1; } - -// In EAMv2, subarea can take 3 values (0,1 and 2), therefore -// length of the maxsubarea is 3 -KOKKOS_INLINE_FUNCTION constexpr int maxsubarea() { return 3; } - -// number of gases used in aerosol microphysics (soag and h2so4) -KOKKOS_INLINE_FUNCTION constexpr int max_gas() { return 2; } - -// Index for h2so4 and nh3 -constexpr int igas_h2so4 = 1; // FIXME: This can change with modal model -constexpr int igas_nh3 = -1; // FIXME: This can change with modal model - -// leave number mix-ratios unchanged (#/kmol-air) -KOKKOS_INLINE_FUNCTION Real fcvt_num() { return 1; } - -// factor for converting aerosol water mix-ratios from (kg/kg) to (mol/mol) -KOKKOS_INLINE_FUNCTION Real fcvt_wtr() { - return haero::Constants::molec_weight_dry_air / - haero::Constants::molec_weight_h2o; -} - -// Returns index of aerosol numbers in gas_pcnst array -KOKKOS_INLINE_FUNCTION int lmap_num(const int mode) { - return numptr_amode_gas_pcnst(mode); -} - -// Returns index of aerosol numbers in gas_pcnst array -KOKKOS_INLINE_FUNCTION int lmap_numcw(const int mode) { - return numptr_amode_gas_pcnst(mode); -} -// aerosol mapping for aerosol microphysics -// NOTE: it is different from "lmassptr_amode_gas_pcnst" as -// amicphys adds aerosol species in a special order that is different from -// lmassptr_amode_gas_pcnst -KOKKOS_INLINE_FUNCTION int lmap_aer(const int iaer, const int mode) { - static constexpr int lmap_aer_[num_aerosol_ids][num_modes] = { - {8, 15, 24, -1}, {6, 14, 21, -1}, {7, -1, 23, 27}, {9, -1, 22, 28}, - {11, 16, 20, -1}, {10, -1, 19, -1}, {12, 17, 25, 29}, - }; - return lmap_aer_[iaer][mode]; -} - -KOKKOS_INLINE_FUNCTION int lmap_aercw(const int iaer, const int mode) { - return lmap_aer(iaer, mode); -} - -constexpr Real mass_2_vol[num_aerosol_ids] = {0.15, - 6.4971751412429377e-002, - 0.15, - 7.0588235294117650e-003, - 3.0789473684210526e-002, - 5.1923076923076926e-002, - 156.20986883198000}; - -// conversion factor for aerosols -// NOTE: The following array has a special order to match amicphys -KOKKOS_INLINE_FUNCTION Real fcvt_aer(const int iaer) { - static constexpr Real fcvt_aer_[num_aerosol_ids] = { - 8.000000000000000E-002, 1, 8.000000000000000E-002, 1, 1, 1, 1}; - return fcvt_aer_[iaer]; -} - -// Number of differently tagged secondary-organic aerosol species -KOKKOS_INLINE_FUNCTION constexpr int nsoa() { return 1; } - -// conversion factor for gases -KOKKOS_INLINE_FUNCTION Real fcvt_gas(const int gas_id) { - // mw to use for soa - // BAD CONSTANTS - constexpr Real mwuse_soa = 150; - // molecular weight of the gas - Real mw_gas = mam4::gas_chemistry::adv_mass[lmap_gas(gas_id)]; - // denominator - Real denom = mw_gas; - // special case for soa - if(gas_id < nsoa()) denom = mwuse_soa; - return mw_gas / denom; -} - -//-------------------------------------------------------------------------------- -// Utility functions -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void copy_1d_array(const int arr_len, const Real (&arr_in)[arr_len], // in - Real (&arr_out)[arr_len]) { // out - for(int ii = 0; ii < arr_len; ++ii) { - arr_out[ii] = arr_in[ii]; - } -} - -KOKKOS_INLINE_FUNCTION -void copy_2d_array(const int first_dimlen, // in - const int second_dimlen, // in - const Real (&arr_in)[first_dimlen][second_dimlen], // in - Real (&arr_out)[first_dimlen][second_dimlen]) { // out - - for(int ifd = 0; ifd < first_dimlen; ++ifd) { - for(int isd = 0; isd < second_dimlen; ++isd) { - arr_out[ifd][isd] = arr_in[ifd][isd]; - } - } -} -template -KOKKOS_INLINE_FUNCTION void assign_1d_array(const int arr_len, - const DT num, // in - DT *arr_out) { // out - for(int ii = 0; ii < arr_len; ++ii) { - arr_out[ii] = num; - } -} - -KOKKOS_INLINE_FUNCTION -void assign_2d_array(const int first_dimlen, // in - const int second_dimlen, // in - const Real num, // in - Real (&arr_out)[first_dimlen][second_dimlen]) { // out - for(int ifd = 0; ifd < first_dimlen; ++ifd) { - for(int isd = 0; isd < second_dimlen; ++isd) { - arr_out[ifd][isd] = num; - } - } -} -// copy 3d arrays -KOKKOS_INLINE_FUNCTION -void assign_3d_array( - const int first_dimlen, // in - const int second_dimlen, // in - const int third_dimlen, // in - const Real num, // in - Real (&arr_out)[first_dimlen][second_dimlen][third_dimlen]) { // out - for(int ifd = 0; ifd < first_dimlen; ++ifd) { - for(int isd = 0; isd < second_dimlen; ++isd) { - for(int itd = 0; itd < third_dimlen; ++itd) { - arr_out[ifd][isd][itd] = num; - } - } - } -} - -//-------------------------------------------------------------------------------- -// Configuration settings -//-------------------------------------------------------------------------------- - -// MAM4 aerosol microphysics configuration data -struct AmicPhysConfig { - // these switches activate various aerosol microphysics processes - bool do_cond; // condensation (a.k.a gas-aerosol exchange) - bool do_rename; // mode "renaming" - bool do_newnuc; // gas -> aerosol nucleation - bool do_coag; // aerosol coagulation - - // controls treatment of h2so4 condensation in mam_gasaerexch_1subarea - // 1 = sequential calc. of gas-chem prod then condensation loss - // 2 = simultaneous calc. of gas-chem prod and condensation loss - int gaexch_h2so4_uptake_optaa; - - // controls how nucleation interprets h2so4 concentrations - int newnuc_h2so4_conc_optaa; -}; - -namespace { - -KOKKOS_INLINE_FUNCTION -void setup_subareas(const Real cld, // in - int &nsubarea, int &ncldy_subarea, // out - int &jclea, int &jcldy, // out - bool (&iscldy_subarea)[(maxsubarea())], // out - Real (&afracsub)[maxsubarea()], // out - Real &fclea, Real &fcldy) // out -{ - //-------------------------------------------------------------------------------------- - // Purpose: Determine the number of sub-areas and their fractional areas. - // Assign values to some bookkeeping variables. - //-------------------------------------------------------------------------------------- - - // cld: cloud fraction in the grid cell [unitless] - // nsubarea: total # of subareas to do calculations for - // ncldy_subarea: total # of cloudy subareas - // jclea, jcldy: indices of the clear and cloudy subareas - // iscldy_subarea(maxsubarea): whether a subarea is cloudy - // afracsub(maxsubarea): area fraction of each active subarea[unitless] - // fclea, fcldy: area fraction of clear/cloudy subarea [unitless] - - // BAD CONSTANT - // Cloud chemistry is only active when cld(i,kk) >= 1.0e-5 - // It may be that the macrophysics has a higher threshold than this - constexpr Real fcld_locutoff = 1.0e-5; - - // BAD CONSTANT - // Grid cells with cloud fraction larger than this cutoff is considered to be - // overcast - constexpr Real fcld_hicutoff = 0.999; - - // if cloud fraction ~= 0, the grid-cell has a single clear sub-area - // (nsubarea = 1) if cloud fraction ~= 1, the grid-cell has a single cloudy - // sub-area (nsubarea = 1) otherwise, the grid-cell has a clear and a cloudy - // sub-area (nsubarea = 2) - - if(cld < fcld_locutoff) { - fcldy = 0; - nsubarea = 1; - ncldy_subarea = 0; - jclea = 1; - jcldy = 0; - } else if(cld > fcld_hicutoff) { - fcldy = 1; - nsubarea = 1; - ncldy_subarea = 1; - jclea = 0; - jcldy = 1; - } else { - fcldy = cld; - nsubarea = 2; - ncldy_subarea = 1; - jclea = 1; - jcldy = 2; - } - - fclea = 1.0 - fcldy; - - // Set up a logical array to indicate whether the subareas are clear or cloudy - // and init area fraction array - for(int jsub = 0; jsub < maxsubarea(); ++jsub) { - iscldy_subarea[jsub] = false; - afracsub[jsub] = 0; - } - - // jcldy>0 can be 1 or 2, so iscldy_subarea(1) or iscldy_subarea(2) is true - if(jcldy > 0) iscldy_subarea[jcldy] = true; - // Save the area fractions to an array - // jclea can only be 1 if jclea > 0, so afracsub (1) is set to fclea - if(jclea > 0) afracsub[jclea] = fclea; - // jcldy can be 1 or 2, so afracsub(1) or afracsub(2) is set to fcldy - if(jcldy > 0) afracsub[jcldy] = fcldy; - -} // setup_subareas - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_rh(const int &ncldy_subarea, const int &jclea, // in - const int &jcldy, // in - const Real (&afracsub)[maxsubarea()], // in - const Real &relhumgcm, // in - Real (&relhumsub)[maxsubarea()]) // out -{ - //---------------------------------------------------------------------------- - // Purpose: Set relative humidity in subareas. - //---------------------------------------------------------------------------- - - // ncldy_subarea :# of cloudy subareas - // jclea, jcldy :indices of clear and cloudy subareas - // afracsub(maxsubarea) :area fraction in subareas [unitless] - // relhumgcm :grid cell mean relative humidity [unitless] - // relhumsub(maxsubarea): relative humidity in subareas [unitless] - - if(ncldy_subarea <= 0) { - // Entire grid cell is cloud-free. RH in subarea = grid cell mean. - // This is clear cell, rehumsub(0),rehumsub(1) and rehumsub(3) are relhumgcm - for(int jsub = 0; jsub < maxsubarea(); ++jsub) relhumsub[jsub] = relhumgcm; - } else { - // Grid cell has a cloudy subarea. Set RH in that part to 1.0. - // jcldy can be 1 or 2 here. - // If jcldy is 1: relhumsub[1] is 1.0 (fully cloudy cell) - // if jcldy is 2: relhumsub[2] is 1.0. In this case jclea is >0, - // so relhumsub[1] is set in if condition below - relhumsub[jcldy] = 1; - - // If the grid cell also has a clear portion, back out the subarea RH from - // the grid-cell mean RH and cloud fraction. - if(jclea > 0) { - // jclea is > 0 only for partly cloudy cell. In this case - // jclea is 1, so relhumsub[1] is set here. - Real relhum_tmp = (relhumgcm - afracsub[jcldy]) / afracsub[jclea]; - relhumsub[jclea] = mam4::utils::min_max_bound(0, 1, relhum_tmp); - } - } -} // set_subarea_rh - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void compute_qsub_from_gcm_and_qsub_of_other_subarea( - const bool (&lcompute)[gas_pcnst], const Real &f_a, // in - const Real &f_b, // in - const Real (&qgcm)[gas_pcnst], // in - const int &jclea, const int &jcldy, // in - Real (&qsub_a)[gas_pcnst][maxsubarea()], // inout - Real (&qsub_b)[gas_pcnst][maxsubarea()]) // inout -{ - //----------------------------------------------------------------------------------------- - // Purpose: Calculate the value of qsub_b assuming qgcm is a weighted average - // defined as - // qgcm = f_a*qsub_a + f_b*qsub_b. - //----------------------------------------------------------------------------------------- - - // f_a, f_b // area fractions [unitless] of subareas - // qgcm(ncnst) // grid cell mean (known) - // qsub_a(ncnst) // value in subarea A (known, but might get adjusted) - // qsub_b(ncnst) // value in subarea B (to be calculated here) - - // Here we populate qsub for subarea index 2 (i.e. jcldy is 2 here) - // and adjust subarea index 1(i.e., jclea is 1 here) if needed. - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - if(lcompute[icnst]) { - // Calculate qsub_b - EKAT_KERNEL_ASSERT_MSG( - f_b != 0, - "Error! compute_qsub_from_gcm_and_qsub_of_other_subarea - f_b is " - "zero\n"); - qsub_b[icnst][jcldy] = (qgcm[icnst] - f_a * qsub_a[icnst][jclea]) / f_b; - // Check that this does not produce a negative value. - // If so, set qsub_b to zero and adjust the value of qsub_a. - if(qsub_b[icnst][jcldy] < 0) { - qsub_b[icnst][jcldy] = 0; - EKAT_KERNEL_ASSERT_MSG( - f_a != 0, - "Error! compute_qsub_from_gcm_and_qsub_of_other_subarea - f_a is " - "zero\n"); - qsub_a[icnst][jclea] = qgcm[icnst] / f_a; - } - } - } -} // compute_qsub_from_gcm_and_qsub_of_other_subarea - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_qnumb_for_cldbrn_aerosols( - const int &jclea, const int &jcldy, const Real &fcldy, // in - const Real (&qqcwgcm)[gas_pcnst], // in - Real (&qqcwsub)[gas_pcnst][maxsubarea()]) // inout -{ - //----------------------------------------------------------------------------------------- - // Purpose: Set the number mixing ratios of cloud-borne aerosols in subareas: - // - zero in clear air; - // - grid-cell-mean divided by cloud-fraction in the cloudy subarea. - // This is done for all lognormal modes. - //----------------------------------------------------------------------------------------- - - // jclea, jcldy : indices of subareas - // fcldy : area fraction [unitless] of the cloudy subarea - // qqcwgcm(ncnst) : grid cell mean (unit does not matter for this - // subr.) - // qqcwsub(ncnst,maxsubarea) : values in subareas (unit does not matter - // for this subr.) - - //---------------------------------------------------------------- - // Here jclea ==1 and jcldy==2 - for(int imode = 0; imode < num_modes; ++imode) { - const int icnst = numptr_amode_gas_pcnst(imode); - qqcwsub[icnst][jclea] = 0; - EKAT_KERNEL_ASSERT_MSG( - fcldy != 0, - "Error! set_subarea_qnumb_for_cldbrn_aerosols - fcldy is " - "zero\n"); - qqcwsub[icnst][jcldy] = qqcwgcm[icnst] / fcldy; - //---------------------------------------------------------------- - } - -} // set_subarea_qnumb_for_cldbrn_aerosols - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_qmass_for_cldbrn_aerosols( - const int &jclea, const int &jcldy, // in - const Real &fcldy, // in - const Real (&qqcwgcm)[gas_pcnst], // in - Real (&qqcwsub)[gas_pcnst][maxsubarea()]) // inout -{ - //----------------------------------------------------------------------------------------- - // Purpose: Set the mass mixing ratios of cloud-borne aerosols in subareas: - // - zero in clear air; - // - grid-cell-mean/cloud-fraction in the cloudy subarea. - // This is done for all lognormal modes and all chemical species. - //----------------------------------------------------------------------------------------- - // jclea, jcldy : subarea indices fcldy : area - // fraction [unitless] of the cloudy subarea - // qqcwgcm(ncnst) : grid cell mean (unit does not matter for this - // subr.) - // qqcwsub(ncnst,maxsubarea) : values in subareas (unit does not matter for - // this subr.) - - //---------------------------------------------------------------- - // Here jclea ==1 and jcldy==2 - - // loop thru all modes - for(int imode = 0; imode < num_modes; ++imode) { - // loop thru all species in a mode - for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { - const int icnst = lmassptr_amode_gas_pcnst(ispec, imode); - - qqcwsub[icnst][jclea] = 0; - EKAT_KERNEL_ASSERT_MSG( - fcldy != 0, - "Error! set_subarea_qmass_for_cldbrn_aerosols - fcldy is " - "zero\n"); - qqcwsub[icnst][jcldy] = qqcwgcm[icnst] / fcldy; - } // ispec - species loop - } // imode - mode loop - //---------------------------------------------------------------- -} // set_subarea_qmass_for_cldbrn_aerosols - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void get_partition_factors(const Real &qgcm_intrst, // in - const Real &qgcm_cldbrn, // in - const Real &fcldy, const Real &fclea, // in - Real &factor_clea, Real &factor_cldy) // out -{ - //------------------------------------------------------------------------------------ - // Purpose: Calculate the partitioning factors for distributing interstitial - // aerosol - // mixing ratios to cloudy and clear subareas in a grid box. - // The partitioning factors depend on the grid cell mean mixing - // ratios of both interstitial and cloud-borne aerosols. - //------------------------------------------------------------------------------------ - - // qgcm_intrst : grid cell mean interstitial aerosol mixing ratio - // qgcm_cldbrn : grid cell mean cloud-borne aerosol mixing ratio - // - // fcldy : cloudy fraction of the grid cell [unitless] - // fclea : clear fraction of the grid cell [unitless] - // - // factor_clea : partitioning factor for clear subarea - // factor_cldy : partitioning factor for cloudy subarea - - // Calculate subarea-mean mixing ratios - - EKAT_KERNEL_ASSERT_MSG(fcldy != 0, - "Error! get_partition_factors - fcldy is " - "zero\n"); - // cloud-borne, cloudy subarea - const Real tmp_q_cldbrn_cldy = qgcm_cldbrn / fcldy; - - // interstitial, cloudy subarea - const Real tmp_q_intrst_cldy = - haero::max(0, ((qgcm_intrst + qgcm_cldbrn) - tmp_q_cldbrn_cldy)); - - EKAT_KERNEL_ASSERT_MSG(fclea != 0, - "Error! get_partition_factors - fclea is " - "zero\n"); - // interstitial, clear subarea - const Real tmp_q_intrst_clea = - (qgcm_intrst - fcldy * tmp_q_intrst_cldy) / fclea; - - // Calculate the corresponding paritioning factors for interstitial - // aerosols using the above-derived subarea-mean mixing ratios plus the - // constraint that the cloud fraction weighted average of subarea mean - // need to match grid box mean. Note that this subroutine is designed for - // partially cloudy grid cells, hence both fclea and fcldy are assumed to - // be nonzero. - - constexpr Real eps = 1.e-35; // BAD CONSTANT - Real clea2gcm_ratio = - haero::max(eps, tmp_q_intrst_clea * fclea) / haero::max(eps, qgcm_intrst); - clea2gcm_ratio = haero::max(0, haero::min(1, clea2gcm_ratio)); - - factor_clea = clea2gcm_ratio / fclea; - factor_cldy = (1 - clea2gcm_ratio) / fcldy; -} // get_partition_factors - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_qnumb_for_intrst_aerosols( - const int &jclea, const int &jcldy, const Real &fclea, // in - const Real &fcldy, const Real (&qgcm)[gas_pcnst], // in - const Real (&qqcwgcm)[gas_pcnst], // in - const Real (&qgcmx)[gas_pcnst], // in - Real (&qsubx)[gas_pcnst][maxsubarea()]) // inout -{ - //----------------------------------------------------------------------------------------- - // Purpose: Set the number mixing ratios of interstitial aerosols in subareas. - // Interstitial aerosols can exist in both cloudy and clear subareas, - // so a grid cell mean needs to be partitioned. Different lognormal - // modes are partitioned differently based on the mode-specific - // number mixing ratios. - //----------------------------------------------------------------------------------------- - - // jclea, jcldy : subarea indices - // fclea, fcldy : area fraction [unitless] of the clear and cloudy subareas - // qgcm (ncnst): grid cell mean, interstitial constituents (unit does not - // matter) - // qqcwgcm(ncnst): grid cell mean, cloud-borne constituents (unit - // does not matter) - - // qgcmx (ncnst): grid cell mean, interstitial constituents (unit does not - // matter) - // qsubx(ncnst,maxsubarea): subarea mixing ratios of interst. constituents - // (unit does not matter as long as they are - // consistent with the grid cell mean values) - - // Note: qgcm and qqcwgcm are used for calculating the patitioning factors. - // qgcmx is the actual grid cell mean that is partitioned into qsubx. - - for(int imode = 0; imode < num_modes; ++imode) { - // calculate partitioning factors - - // grid cell mean of interstitial aerosol mixing ratio of a single mode - const Real qgcm_intrst = qgcm[numptr_amode_gas_pcnst(imode)]; - - // grid cell mean of cloud-borne aerosol mixing ratio of a single mode - const Real qgcm_cldbrn = qqcwgcm[numptr_amode_gas_pcnst(imode)]; - - Real factor_clea; // partitioning factor for clear subarea [unitless] - Real factor_cldy; // partitioning factor for cloudy subarea [unitless] - get_partition_factors(qgcm_intrst, qgcm_cldbrn, fcldy, fclea, // in - factor_clea, factor_cldy); // out - - // apply partitioning factors - const int icnst = numptr_amode_gas_pcnst(imode); - - qsubx[icnst][jclea] = qgcmx[icnst] * factor_clea; - qsubx[icnst][jcldy] = qgcmx[icnst] * factor_cldy; - } // imode - -} // set_subarea_qnumb_for_intrst_aerosols - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_qmass_for_intrst_aerosols( - const int &jclea, const int &jcldy, const Real &fclea, // in - const Real &fcldy, const Real (&qgcm)[gas_pcnst], // in - const Real (&qqcwgcm)[gas_pcnst], // in - const Real (&qgcmx)[gas_pcnst], // in - Real (&qsubx)[gas_pcnst][maxsubarea()]) // inout -{ - //----------------------------------------------------------------------------------------- - // Purpose: Set the mass mixing ratios of interstitial aerosols in subareas. - // Interstitial aerosols can exist in both cloudy and clear subareas, - // so a grid cell mean needs to be partitioned. Different lognormal - // modes are partitioned differently based on the mode-specific - // mixing ratios. All species in the same mode are partitioned the - // same way, consistent with the internal mixing assumption used in - // MAM. - //----------------------------------------------------------------------------------------- - - // jclea, jcldy : subarea indices - // fclea, fcldy : area fraction [unitless] of the clear and cloudy subareas - // qgcm (ncnst) : grid cell mean, interstitial constituents (unit does not - // matter) - // qqcwgcm(ncnst) : grid cell mean, cloud-borne constituents (unit - // does not matter) - - // qgcmx (ncnst) : grid cell mean, interstitial constituents (unit does not - // matter) - // qsubx(ncnst,maxsubarea): subarea mixing ratios of interst. - // constituents(unit does not matter as long as they - // are consistent with the grid cell mean values) - - // Note: qgcm and qqcwgcm are used for calculating the patitioning factors. - // qgcmx is the actual grid cell mean that is partitioned into qsubx. - - for(int imode = 0; imode < num_modes; ++imode) { - // calculcate partitioning factors - - // grid cell mean of interstitial aerosol mixing ratio of a single mode - Real qgcm_intrst = 0; - - // grid cell mean of cloud-borne aerosol mixing ratio of a single mode - Real qgcm_cldbrn = 0; - - // loop thru all species in a mode - for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { - qgcm_intrst = qgcm_intrst + qgcm[lmassptr_amode_gas_pcnst(ispec, imode)]; - qgcm_cldbrn = - qgcm_cldbrn + qqcwgcm[lmassptr_amode_gas_pcnst(ispec, imode)]; - } - - Real factor_clea; // partitioning factor for clear subarea [unitless] - Real factor_cldy; // partitioning factor for cloudy subarea [unitless] - get_partition_factors(qgcm_intrst, qgcm_cldbrn, fcldy, fclea, // in - factor_clea, factor_cldy); // out - - // apply partitioning factors - // Here jclea==1 and jcldy==2 - for(int ispec = 0; ispec < mam4::num_species_mode(imode); ++ispec) { - const int icnst = lmassptr_amode_gas_pcnst(ispec, imode); - qsubx[icnst][jclea] = qgcmx[icnst] * factor_clea; - qsubx[icnst][jcldy] = qgcmx[icnst] * factor_cldy; - } // ispec - } // imode - -} // set_subarea_qmass_for_intrst_aerosols - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void set_subarea_gases_and_aerosols( - const int &nsubarea, const int &jclea, // in - const int &jcldy, // in - const Real &fclea, const Real &fcldy, // in - const Real (&qgcm1)[gas_pcnst], const Real (&qgcm2)[gas_pcnst], // in - const Real (&qqcwgcm2)[gas_pcnst], // in - const Real (&qgcm3)[gas_pcnst], // in - const Real (&qqcwgcm3)[gas_pcnst], // in - Real (&qsub1)[gas_pcnst][maxsubarea()], // out - Real (&qsub2)[gas_pcnst][maxsubarea()], // out - Real (&qqcwsub2)[gas_pcnst][maxsubarea()], // out - Real (&qsub3)[gas_pcnst][maxsubarea()], // out - Real (&qqcwsub3)[gas_pcnst][maxsubarea()]) // out -{ - //------------------------------------------------------------------------------------------------ - // Purpose: Partition grid cell mean mixing ratios to clear/cloudy subareas. - //------------------------------------------------------------------------------------------------ - // nsubarea: # of active subareas in the current grid cell - // jclea, jcldy: indices of the clear and cloudy subareas - // fclea, fcldy: area fractions of the clear and cloudy subareas [unitless] - - // The next set of argument variables are tracer mixing ratios. - // - The units are different for gases, aerosol number, and aerosol mass. - // The exact units do not matter for this subroutine, as long as the - // grid cell mean values ("gcm") and the corresponding subarea values - // ("sub") have the same units. - // - qq* and qqcw* are correspond to the interstitial and cloud-borne - // species, respectively - // - The numbers 1-3 correspond to different locations in the host model's - // time integration loop. - - // Grid cell mean mixing ratios - // qgcm1(ncnst), qgcm2(ncnst), qqcwgcm2(ncnst), qgcm3(ncnst), - // qqcwgcm3(ncnst) - - // Subarea mixing ratios - // qsub1(ncnst,maxsubarea), qsub2(ncnst,maxsubarea), - // qsub3(ncnst,maxsubarea), qqcwsub2(ncnst,maxsubarea) - // qqcwsub3(ncnst,maxsubarea) - //---- - - //------------------------------------------------------------------------------------ - // Initialize mixing ratios in subareas before the aerosol microphysics - // calculations - //------------------------------------------------------------------------------------ - // FIXME:Should we set jsub==0 a special value (like NaNs) so that it is never - // used?? - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - for(int jsub = 0; jsub < maxsubarea(); ++jsub) { - // Gases and interstitial aerosols - qsub1[icnst][jsub] = 0; - qsub2[icnst][jsub] = 0; - qsub3[icnst][jsub] = 0; - - // Cloud-borne aerosols - qqcwsub2[icnst][jsub] = 0; - qqcwsub3[icnst][jsub] = 0; - } - } - //--------------------------------------------------------------------------------------------------- - // Determine which category the current grid cell belongs to: partly cloudy, - // all cloudy, or all clear - //--------------------------------------------------------------------------------------------------- - const bool grid_cell_has_only_clea_area = - ((jclea == 1) && (jcldy == 0) && (nsubarea == 1)); - const bool grid_cell_has_only_cldy_area = - ((jclea == 0) && (jcldy == 1) && (nsubarea == 1)); - const bool gird_cell_is_partly_cldy = - (jclea > 0) && (jcldy > 0) && (jclea + jcldy == 3) && (nsubarea == 2); - - // Sanity check - if((!grid_cell_has_only_clea_area) && (!grid_cell_has_only_cldy_area) && - (!gird_cell_is_partly_cldy)) { - EKAT_KERNEL_ASSERT_MSG(true, - "Error! modal_aero_amicphys - bad jclea, jcldy, " - "nsubarea, jclea, jcldy, nsubarea\n"); - } - - //************************************************************************************************* - // Category I: grid cell is either all clear or all cloudy. Copy the grid - // cell mean values. - //************************************************************************************************* - if(grid_cell_has_only_clea_area || grid_cell_has_only_cldy_area) { - // For fully clear and cloudy cells, we populate only 1st index of subarea - // for all output vars - // Makes sense as there is only 1 subarea for these cases. - // FIXME: Should we fill in NaNs for the 0th and 2nd index?? - constexpr int jsub = 1; - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - // copy all gases and aerosols - if(lmapcc_all(icnst) > 0) { - qsub1[icnst][jsub] = qgcm1[icnst]; - qsub2[icnst][jsub] = qgcm2[icnst]; - qsub3[icnst][jsub] = qgcm3[icnst]; - - qqcwsub2[icnst][jsub] = qqcwgcm2[icnst]; - qqcwsub3[icnst][jsub] = qqcwgcm3[icnst]; - } - } - } // if only clear or only cloudy - //************************************************************************************************* - // Category II: partly cloudy grid cell. Tracer mixing ratios are generally - // assumed different in clear and cloudy subareas. This is primarily - // because the interstitial aerosol mixing ratios are assumed to be lower - // in the cloudy sub-area than in the clear sub-area, as much of the - // aerosol is activated in the cloudy sub-area. - //************************************************************************************************* - else if(gird_cell_is_partly_cldy) { - //=================================== - // Set gas mixing ratios in subareas - //=================================== - //------------------------------------------------------------------------------------------ - // Before gas chemistry, gas mixing ratios are assumed to be the same in - // all subareas, i.e., they all equal the grid cell mean. - //------------------------------------------------------------------------------------------ - - // NOTE: In this "else if" case jclea == 1 and jcldy == 2 - - bool cnst_is_gas[gas_pcnst] = {}; - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - cnst_is_gas[icnst] = (lmapcc_all(icnst) == lmapcc_val_gas()); - } - - EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), - "Error! set_subarea_gases_and_aerosols: " - "nsubarea should be < maxsubarea() \n"); - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - if(cnst_is_gas[icnst]) { - // For gases, assume both 1 and 2 subareas have grid mean values - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - qsub1[icnst][jsub] = qgcm1[icnst]; - } - } - } - // qsub1 is fully populated for gasses - //------------------------------------------------------------------------------------------ - // After gas chemistry, still assume gas mixing ratios are the same in all - // subareas. - //------------------------------------------------------------------------------------------ - - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - if(cnst_is_gas[icnst]) { - // For gases, assume both 1 and 2 subareas have grid mean values - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - qsub2[icnst][jsub] = qgcm2[icnst]; - } - } - } - // qsub2 is fully populated for gasses - //---------------------------------------------------------------------------------------- - // After cloud chemistry, gas and aerosol mass mixing ratios in the clear - // subarea are assumed to be the same as their values before cloud - // chemistry (because by definition, cloud chemistry did not happen in - // clear sky), while the mixing ratios in the cloudy subarea likely have - // changed. - //---------------------------------------------------------------------------------------- - // Gases in the clear subarea remain the same as their values before cloud - // chemistry. - // Here we populate qsub3 for index 1 only as jclea is 1. - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - if(cnst_is_gas[icnst]) { - qsub3[icnst][jclea] = qsub2[icnst][jclea]; - } - } - - // Calculate the gas mixing ratios in the cloudy subarea using the - // grid-cell mean, cloud fraction and the clear-sky values - // Here we populate qsub3 for index 2 (jcldy) and adjust index 1 (jclea) if - // needed. - compute_qsub_from_gcm_and_qsub_of_other_subarea(cnst_is_gas, fclea, // in - fcldy, qgcm3, jclea, // in - jcldy, // in - qsub3, qsub3); // inout - // qsub3[2][2]); - // qsub3 is fully populated for gasses - //========================================================================= - // Set AEROSOL mixing ratios in subareas. - // Only need to do this for points 2 and 3 in the time integraion loop, - // i.e., the before-cloud-chem and after-cloud-chem states. - //========================================================================= - // Cloud-borne aerosols. (They are straightforward to partition, - // as they only exist in the cloudy subarea.) - //---------------------------------------------------------------------------------------- - // Partition mass and number before cloud chemistry - // NOTE that in this case jclea is 1 and jcldy is 2 - // Following 2 calls set qqcwsub2(:,1)=0 and qqcwsub2(:,2) to a computed - // value - set_subarea_qnumb_for_cldbrn_aerosols(jclea, jcldy, fcldy, - qqcwgcm2, // in - qqcwsub2); // inout - - set_subarea_qmass_for_cldbrn_aerosols(jclea, jcldy, fcldy, - qqcwgcm2, // in - qqcwsub2); // inout - // Partition mass and number before cloud chemistry - // Following 2 calls set qqcwsub3(:,1)=0 and qqcwsub3(:,2) to a computed - // value - set_subarea_qnumb_for_cldbrn_aerosols(jclea, jcldy, fcldy, - qqcwgcm3, // in - qqcwsub3); // inout - set_subarea_qmass_for_cldbrn_aerosols(jclea, jcldy, fcldy, - qqcwgcm3, // in - qqcwsub3); // inout - - //---------------------------------------------------------------------------------------- - // Interstitial aerosols. (They can exist in both cloudy and clear - // subareas, and hence need to be partitioned.) - //---------------------------------------------------------------------------------------- - // Partition mass and number before cloud chemistry - // Following 2 calls set qsub2(:,1) = 0 and qsub2(:,2) to a computed value - set_subarea_qnumb_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in - qgcm2, qqcwgcm2, qgcm2, // in - qsub2); // inout - - set_subarea_qmass_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in - qgcm2, qqcwgcm2, qgcm2, // in - qsub2); // inout - - // Partition mass and number before cloud chemistry - // Following 2 calls set qsub3(:,1) = 0 and qsub3(:,2) to a computed value - set_subarea_qnumb_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in - qgcm2, qqcwgcm2, qgcm3, // in - qsub3); // inout - - set_subarea_qmass_for_intrst_aerosols(jclea, jcldy, fclea, fcldy, // in - qgcm2, qqcwgcm2, qgcm3, // in - qsub3); // inout - - } // different categories -} // set_subarea_gases_and_aerosols - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void mam_newnuc_1subarea( - const int igas_h2so4, const int gaexch_h2so4_uptake_optaa, // in - const int newnuc_h2so4_conc_optaa, const int jsub, // in - const Real deltat, const Real temp, const Real pmid, // in - const Real aircon, const Real zmid, const Real pblh, // in - const Real relhum, const Real uptkrate_h2so4, // in - const Real del_h2so4_gasprod, const Real del_h2so4_aeruptk, // in - Real qgas_cur[max_gas()], const Real qgas_avg[max_gas()], // out - Real qnum_cur[AeroConfig::num_modes()], // out - Real qaer_cur[AeroConfig::num_aerosol_ids()] - [AeroConfig::num_modes()], // out - Real dnclusterdt) { // out - // FIXME: This function was not refactored or cleaned in fortran - // we must clean it and remove unused codes and fix variable names - - // nstep: model time-step number - // jsub: sub-area index - // deltat: model timestep (s) - // temp: temperature (K) - // pmid: pressure at model levels(Pa) - // aircon: air molar concentration (kmol/m3) - // zmid: midpoint height above surface (m) - // pblh :pbl height (m) - // relhum:relative humidity (0-1) - // uptkrate_h2so4 - // del_h2so4_gasprod - // del_h2so4_aeruptk - // dnclusterdt: cluster nucleation rate (#/m3/s) - // qgas_cur(max_gas) - // qgas_avg(max_gas) - // qnum_cur(max_mode) - // qaer_cur(1:max_aer,1:max_mode) - // qwtr_cur(1:max_mode) - - // DESCRIPTION: - // computes changes due to aerosol nucleation (new particle formation) - // treats both nucleation and subsequent growth of new particles - // to aitken mode size - // uses the following parameterizations - // vehkamaki et al. (2002) parameterization for binary - // homogeneous nucleation (h2so4-h2o) plus - // kerminen and kulmala (2002) parameterization for - // new particle loss during growth to aitken size - // - // REVISION HISTORY: - // R.Easter 2007.09.14: Adapted from MIRAGE2 code and CMAQ V4.6 code - // - - constexpr int newnuc_method_flagaa = 11; - // 1=merikanto et al (2007) ternary 2=vehkamaki et al (2002) binary - // 11=merikanto ternary + first-order boundary layer - // 12=merikanto ternary + second-order boundary layer - - // begin - dnclusterdt = 0; - - // qh2so4_cur = current qh2so4, after aeruptk - // qh2so4_avg = average qh2so4 over time-step - // BAD CONSTANTS - constexpr Real qh2so4_cutoff = 4.0e-16; - Real qh2so4_cur = qgas_cur[igas_h2so4]; - Real qh2so4_avg, tmp_uptkrate, tmpa; - - // use qh2so4_avg and first-order loss rate calculated in - // mam_gasaerexch_1subarea - qh2so4_avg = qgas_avg[igas_h2so4]; - tmp_uptkrate = uptkrate_h2so4; - - if(qh2so4_avg <= qh2so4_cutoff) return; - - static constexpr int igas_nh3 = -999888777; // Same as mam_refactor - Real qnh3_cur = 0; - - // dry-diameter limits for "grown" new particles - constexpr int nait = static_cast(ModeIndex::Aitken); - Real dplom_mode = haero::exp(0.67 * haero::log(modes(nait).min_diameter) + - 0.33 * haero::log(modes(nait).nom_diameter)); - Real dphim_mode = modes(nait).max_diameter; - - // mass1p_... = mass (kg) of so4 & nh4 in a single particle of diameter ... - // (assuming same dry density for so4 & nh4) - // mass1p_aitlo - dp = dplom_mode - // mass1p_aithi - dp = dphim_mode - constexpr Real dens_so4a_host = 1770; - tmpa = dens_so4a_host * haero::Constants::pi / 6.0; - Real mass1p_aitlo = tmpa * (haero::pow(dplom_mode, 3.0)); - Real mass1p_aithi = tmpa * (haero::pow(dphim_mode, 3.0)); - - // limit RH to between 0.1% and 99% - Real relhumnn = haero::max(0.01, haero::min(0.99, relhum)); - - // BAD CONSTANTS (These should come from chemistry mechanism - // but it is fixed here for stay BFB) - constexpr Real mw_so4a_host = 115; - constexpr Real mwnh4 = 18; - constexpr Real mwso4 = 96; - // BAD CONSTANTS (These should come from Haero constants - // but it is fixed here for stay BFB) - constexpr Real rgas = 8.31446759100000; - constexpr Real avogadro = 6.022140000000000E+023; - - int itmp; - Real qnuma_del, qso4a_del, qnh4a_del, qh2so4_del, qnh3_del, dens_nh4so4a; - - // call ... routine to get nucleation rates - // FIXME: I GOT ALL ZEROS...THIS IS NOT VALIDATED YET!!!! - mam4::nucleation::mer07_veh02_nuc_mosaic_1box( - newnuc_method_flagaa, deltat, temp, relhumnn, pmid, zmid, pblh, // in - qh2so4_cur, qh2so4_avg, qnh3_cur, tmp_uptkrate, mw_so4a_host, 1, // in - dplom_mode, dphim_mode, rgas, avogadro, mwnh4, mwso4, // in - haero::Constants::pi, // in - itmp, qnuma_del, qso4a_del, qnh4a_del, qh2so4_del, // out - qnh3_del, dens_nh4so4a, dnclusterdt); // out - - // convert qnuma_del from (#/mol-air) to (#/kmol-air) - qnuma_del = qnuma_del * 1.0e3; - - // number nuc rate (#/kmol-air/s) from number nuc amt - Real dndt_ait = qnuma_del / deltat; - - // fraction of mass nuc going to so4 - tmpa = qso4a_del * mw_so4a_host; - Real tmpb = tmpa; - Real tmp_frso4 = 1.0; - - // mass nuc rate (kg/kmol-air/s) from mass nuc amts - EKAT_KERNEL_ASSERT_MSG(deltat != 0, - "Error! mam_newnuc_1subarea: " - " deltat should not be equal to 0\n"); - Real dmdt_ait = haero::max(0.0, (tmpb / deltat)); - - Real dndt_aitsv2 = 0.0; - Real dmdt_aitsv2 = 0.0; - Real dndt_aitsv3 = 0.0; - Real dmdt_aitsv3 = 0.0; - // BAD CONSTANTS - if(dndt_ait < 1.0e2) { - // ignore newnuc if number rate < 100 #/kmol-air/s ~= 0.3 #/mg-air/d - dndt_ait = 0.0; - dmdt_ait = 0.0; - } else { - dndt_aitsv2 = dndt_ait; - dmdt_aitsv2 = dmdt_ait; - - // mirage2 code checked for complete h2so4 depletion here, - // but this is now done in mer07_veh02_nuc_mosaic_1box - EKAT_KERNEL_ASSERT_MSG(dndt_ait != 0, - "Error! mam_newnuc_1subarea: " - " dndt_ait should not be equal to 0\n"); - Real mass1p = dmdt_ait / dndt_ait; - dndt_aitsv3 = dndt_ait; - dmdt_aitsv3 = dmdt_ait; - - EKAT_KERNEL_ASSERT_MSG(mass1p_aitlo != 0, - "Error! mam_newnuc_1subarea: " - " mass1p_aitlo should not be equal to 0\n"); - // apply particle size constraints - if(mass1p < mass1p_aitlo) { - // reduce dndt to increase new particle size - dndt_ait = dmdt_ait / mass1p_aitlo; - } else if(mass1p > mass1p_aithi) { - // reduce dmdt to decrease new particle size - dmdt_ait = dndt_ait * mass1p_aithi; - } - } - - // *** apply adjustment factor to avoid unrealistically high - // aitken number concentrations in mid and upper troposphere - constexpr Real newnuc_adjust_factor_dnaitdt = 1; - dndt_ait = dndt_ait * newnuc_adjust_factor_dnaitdt; - dmdt_ait = dmdt_ait * newnuc_adjust_factor_dnaitdt; - - Real tmp_q_del = dndt_ait * deltat; - qnum_cur[nait] = qnum_cur[nait] + tmp_q_del; - - // dso4dt_ait, dnh4dt_ait are (kmol/kmol-air/s) - - constexpr Real mw_nh4a_host = mw_so4a_host; - EKAT_KERNEL_ASSERT_MSG(mw_so4a_host != 0, - "Error! mam_newnuc_1subarea: " - " mw_so4a_host should not be equal to 0\n"); - Real dso4dt_ait = dmdt_ait * tmp_frso4 / mw_so4a_host; - EKAT_KERNEL_ASSERT_MSG(mw_nh4a_host != 0, - "Error! mam_newnuc_1subarea: " - " mw_nh4a_host should not be equal to 0\n"); - Real dnh4dt_ait = dmdt_ait * (1.0 - tmp_frso4) / mw_nh4a_host; - constexpr int iaer_so4 = 1; - if(dso4dt_ait > 0.0) { - tmp_q_del = dso4dt_ait * deltat; - qaer_cur[iaer_so4][nait] = qaer_cur[iaer_so4][nait] + tmp_q_del; - tmp_q_del = haero::min(tmp_q_del, qgas_cur[igas_h2so4]); - qgas_cur[igas_h2so4] = qgas_cur[igas_h2so4] - tmp_q_del; - } -} // end mam_newnuc_1subarea -//-------------------------------------------------------------------------------- -// Call aerosol microphysics processes for a single (cloudy or clear) subarea -// -// qgas3, qaer3, qaercw3, qnum3, qnumcw3 are the current incoming TMRs -// qgas_cur, qaer_cur, qaercw_cur, qnum_cur, qnumcw_cur are the updated -// outgoing TMRs -// -// In a clear subarea, calculate -// - gas-aerosol exchange (condensation/evaporation) -// - growth from smaller to larger modes (renaming) due to condensation -// - new particle nucleation -// - coagulation -// - transfer of particles from hydrophobic modes to hydrophilic modes -// (aging) -// due to condensation and coagulation -// -// In a cloudy subarea, -// - when do_cond = false, this routine only calculate changes involving -// growth from smaller to larger modes (renaming) following cloud chemistry -// so gas TMRs are not changed -// - when do_cond = true, this routine also calculates changes involving -// gas-aerosol exchange (condensation/evaporation) -// - transfer of particles from hydrophobic modes to hydrophilic modes -// (aging) -// due to condensation -// Currently, in a cloudy subarea, this routine does not do -// - new particle nucleation - because h2so4 gas conc. should be very low in -// cloudy air -// - coagulation - because cloud-borne aerosol would need to be included -//-------------------------------------------------------------------------------- -KOKKOS_INLINE_FUNCTION -void mam_amicphys_1subarea( - // in - const int newnuc_h2so4_conc_optaa, const int gaexch_h2so4_uptake_optaa, - const bool do_cond_sub, const bool do_rename_sub, const bool do_newnuc_sub, - const bool do_coag_sub, const Real deltat, const int jsubarea, - const bool iscldy_subarea, const Real afracsub, const Real temp, - const Real pmid, const Real pdel, const Real zmid, const Real pblh, - const Real relhumsub, const Real (&dgn_a)[num_modes], - const Real (&dgn_awet)[num_modes], const Real (&wetdens)[num_modes], - const Real (&qgas1)[max_gas()], const Real (&qgas3)[max_gas()], - // inout - Real (&qgas_cur)[max_gas()], Real (&qgas_delaa)[max_gas()][nqtendaa()], - // in - const Real (&qnum3)[num_modes], - // inout - Real (&qnum_cur)[num_modes], Real (&qnum_delaa)[num_modes][nqtendaa()], - // in - const Real (&qaer2)[num_aerosol_ids][num_modes], - const Real (&qaer3)[num_aerosol_ids][num_modes], - // inout - Real (&qaer_cur)[num_aerosol_ids][num_modes], - Real (&qaer_delaa)[num_aerosol_ids][num_modes][nqtendaa()], - // in - const Real (&qwtr3)[num_modes], - // inout - Real (&qwtr_cur)[num_modes], - // in - const Real (&qnumcw3)[num_modes], - // inout - Real (&qnumcw_cur)[num_modes], - Real (&qnumcw_delaa)[num_modes][nqqcwtendaa()], - // in - const Real (&qaercw2)[num_aerosol_ids][num_modes], - const Real (&qaercw3)[num_aerosol_ids][num_modes], - // inout - Real (&qaercw_cur)[num_aerosol_ids][num_modes], - Real (&qaercw_delaa)[num_aerosol_ids][num_modes][nqqcwtendaa()]) - -{ - // do_cond_sub, do_rename_sub: true if the aero. microp. process is - // calculated in this subarea - // do_newnuc_sub, do_coag_sub: true if the aero. microp. process is - // calculated in this subarea - // iscldy_subarea: true if sub-area is cloudy - // kk: level indices - // jsubarea, nsubarea: sub-area index, number of sub-areas - // afracsub: fractional area of subarea [unitless, 0-1] - // deltat: time step [s] - // temp: air temperature at model levels [K] - // pmid: air pressure at layer center [Pa] - // pdel: pressure thickness of layer [Pa] - // zmid: altitude (above ground) at layer center [m] - // pblh: planetary boundary layer depth [m] - // relhum: relative humidity [unitless, 0-1] - // dgn_a (max_mode): dry geo. mean diameter [m] of number distribution - // dgn_awet(max_mode): wet geo. mean diameter [m] of number distribution - // wetdens (max_mode): interstitial aerosol wet density [kg/m3] - - // Subare mixing ratios qXXXN (X=gas,aer,wat,num; N=1:4): - // - // XXX=gas - gas species [kmol/kmol] - // XXX=aer - aerosol mass species (excluding water) [kmol/kmol] - // XXX=wat - aerosol water [kmol/kmol] - // XXX=num - aerosol number [#/kmol] - // - // N=1 - before gas-phase chemistry - // N=2 - before cloud chemistry - // N=3 - current incoming values (before gas-aerosol exchange, newnuc, - // coag) N=_cur - updated outgoing values (after gas-aerosol exchange, - // newnuc, coag) - // - // qgas1, qgas3 [kmol/kmol] - // qgas_cur [kmol/kmol] - - // qnum3 [#/kmol] - // qnum_cur [#/kmol] - - // qaer2, qaer3 [kmol/kmol] - // qaer_cur[kmol/kmol] - - // qnumcw3[#/kmol] - // qnumcw_cur [#/kmol] - - // qaercw2, qaercw3 [kmol/kmol] - // qaercw_cur [kmol/kmol] - - // qwtr3 [kmol/kmol] - // qwtr_cur [kmol/kmol] - - // qXXX_delaa are TMR changes (increments, not tendencies) of different - // microphysics processes. These are diagnostics sent to history output; - // they do not directly affect time integration. - - // qgas_delaa [kmol/kmol] - // qnum_delaa [ #/kmol] - // qaer_delaa [kmol/kmol] - // qnumcw_delaa [ #/kmol] - // qaercw_delaa [kmol/kmol] - - // type ( misc_vars_aa_type ), intent(inout) :: misc_vars_aa_sub - - //--------------------------------------------------------------------------------------- - // Calculate air molar density [kmol/m3] to be passed on to individual - // parameterizations - //--------------------------------------------------------------------------------------- - // BAD CONSTANT - // Universal gas constant (J/K/kmol) - constexpr Real r_universal = 8314.46759100000; - const Real aircon = pmid / (r_universal * temp); - - //---------------------------------------------------------- - // Initializ mixing ratios with the before-amicphys values - //---------------------------------------------------------- - - copy_1d_array(max_gas(), qgas3, // in - qgas_cur); // out - - constexpr int nspecies = num_aerosol_ids; - constexpr int nmodes = num_modes; - - copy_2d_array(nspecies, nmodes, qaer3, // in - qaer_cur); // out - - copy_1d_array(nmodes, qnum3, // in - qnum_cur); // out - - copy_1d_array(nmodes, qwtr3, // in - qwtr_cur); // out - - if(iscldy_subarea) { - copy_1d_array(nmodes, qnumcw3, // in - qnumcw_cur); // out - copy_2d_array(nspecies, nmodes, qaercw3, // in - qaercw_cur); // out - } // iscldy_subarea - - //--------------------------------------------------------------------- - // Diagnose net production rate of H2SO4 gas production - // cause by other processes (e.g., gas chemistry and cloud chemistry) - //--------------------------------------------------------------------- - Real qgas_netprod_otrproc[max_gas()] = {0}; - assign_1d_array(max_gas(), 0.0, // in - qgas_netprod_otrproc); // out - - // If gaexch_h2so4_uptake_optaa == 2, then - // - if qgas increases from pre-gaschem to post-cldchem, - // start from the pre-gaschem mix-ratio and add in the production during - // the integration - // - if it decreases, start from post-cldchem mix-ratio - - if((do_cond_sub) && (gaexch_h2so4_uptake_optaa == 2)) { - for(int igas = 0; igas < max_gas(); ++igas) { - if((igas == igas_h2so4) || (igas == igas_nh3)) { - qgas_netprod_otrproc[igas] = (qgas3[igas] - qgas1[igas]) / deltat; - qgas_cur[igas] = (qgas_netprod_otrproc[igas] >= 0) ? qgas1[igas] : 0; - } // h2so4, igas_nh3 - } // igas - } // do_cond_sub,gaexch_h2so4_uptake_optaa - - constexpr int ntsubstep = 1; - const Real del_h2so4_gasprod = - haero::max(qgas3[igas_h2so4] - qgas1[igas_h2so4], 0) / ntsubstep; - //----------------------------------- - // Initialize increment diagnostics - //----------------------------------- - - assign_2d_array(max_gas(), nqtendaa(), 0, // in - qgas_delaa); // out - - assign_2d_array(nmodes, nqtendaa(), 0, // in - qnum_delaa); // out - - assign_3d_array(nspecies, nmodes, nqtendaa(), 0, // in - qaer_delaa); // out - - assign_2d_array(nmodes, nqqcwtendaa(), 0, // in - qnumcw_delaa); // out - - assign_3d_array(nspecies, nmodes, nqqcwtendaa(), 0, // in - qaercw_delaa); // out - - Real ncluster_tend_nnuc_1grid = 0; - - //*********************************** - // loop over multiple time sub-steps - //*********************************** - EKAT_KERNEL_ASSERT_MSG(ntsubstep != 0, - "Error! mam_amicphys_1subarea: " - " ntsubstep should not be equal to 0\n"); - const int dtsubstep = deltat / ntsubstep; - - Real qgas_sv1[max_gas()]; - Real qnum_sv1[nmodes]; - Real qaer_sv1[nspecies][nmodes]; - - Real del_h2so4_aeruptk; // [kmol/kmol] - Real qgas_avg[max_gas()]; // [kmol/kmol] - - // Mixing ratio increments of sub-timesteps used for process coupling - - Real qnum_delsub_cond[nmodes]; // [ #/kmol] - Real qnum_delsub_coag[nmodes]; // [ #/kmol] - Real qaer_delsub_cond[nspecies][nmodes]; // [ #/kmol] - Real qaer_delsub_coag[nspecies][nmodes]; // [kmol/kmol] - Real qaer_delsub_grow4rnam[nspecies][nmodes]; // [kmol/kmol] - Real qaercw_delsub_grow4rnam[nspecies][nmodes]; // [kmol/kmol] - - constexpr int max_agepair = AeroConfig::max_agepair(); - Real qaer_delsub_coag_in[nspecies][max_agepair]; // [kmol/kmol] - - for(int jtsubstep = 0; jtsubstep < ntsubstep; ++jtsubstep) { - //====================== - // Gas-aerosol exchange - //====================== - Real uptkrate_h2so4 = 0; - - if(do_cond_sub) { - copy_1d_array(max_gas(), qgas_cur, // in - qgas_sv1); // out - copy_1d_array(nmodes, qnum_cur, // in - qnum_sv1); // out - - copy_2d_array(nspecies, nmodes, qaer_cur, // in - qaer_sv1); // out - - // max_mode in MAM4 is different from nmodes(max_mode = nmodes+1) - // Here we create temporary arrays for now, but we should make - // it consistent to avoid this extra memoery - constexpr int max_mode = nmodes + 1; - Real qaer_cur_tmp[nspecies][max_mode]; - Real qnum_cur_tmp[max_mode]; - Real qwtr_cur_tmp[max_mode]; - - // NOTE: we cannot use copy_2d_array here as arrays extent is max_mode - // but we are copying till nmodes - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_cur_tmp[is][im] = qaer_cur[is][im]; - } - } - - copy_1d_array(nmodes, qwtr_cur, // in - qwtr_cur_tmp); // out - - copy_1d_array(nmodes, qnum_cur, // in - qnum_cur_tmp); // out - - Real uptkaer[max_gas()][max_mode]; - - mam4::mam_gasaerexch_1subarea( - jtsubstep, dtsubstep, temp, pmid, aircon, nmodes, // in - qgas_cur, qgas_avg, // inout - qgas_netprod_otrproc, // in - qaer_cur_tmp, qnum_cur_tmp, qwtr_cur_tmp, // inout - dgn_awet, // in - uptkaer, uptkrate_h2so4); // inout - - // copy back the values for aerosols - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_cur[is][im] = qaer_cur_tmp[is][im]; - } - } - - copy_1d_array(nmodes, qwtr_cur_tmp, // in - qwtr_cur); // out - - copy_1d_array(nmodes, qnum_cur_tmp, // in - qnum_cur); // out - - for(int ig = 0; ig < max_gas(); ++ig) { - qgas_delaa[ig][iqtend_cond()] = - qgas_delaa[ig][iqtend_cond()] + - (qgas_cur[ig] - - (qgas_sv1[ig] + qgas_netprod_otrproc[ig] * dtsubstep)); - } - for(int im = 0; im < nmodes; ++im) { - qnum_delsub_cond[im] = qnum_cur[im] - qnum_sv1[im]; - } - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delsub_cond[is][im] = qaer_cur[is][im] - qaer_sv1[is][im]; - } - } - - del_h2so4_aeruptk = - qgas_cur[igas_h2so4] - - (qgas_sv1[igas_h2so4] + qgas_netprod_otrproc[igas_h2so4] * dtsubstep); - - } else { // do_cond_sub - copy_1d_array(max_gas(), qgas_cur, // in - qgas_avg); // out - - assign_2d_array(nspecies, nmodes, 0, // in - qaer_delsub_cond); // out - - assign_1d_array(nmodes, 0.0, // in - qnum_delsub_cond); // out - del_h2so4_aeruptk = 0; - - } // do_cond_sub - - //==================================== - // Renaming after "continuous growth" - //==================================== - if(do_rename_sub) { - constexpr int dest_mode_of_mode[nmodes] = {-1, 0, -1, -1}; - - //--------------------------------------------------------- - // Calculate changes in aerosol mass mixing ratios due to - // - gas condensation/evaporation - // - cloud chemistry (if the subarea is cloudy) - //--------------------------------------------------------- - copy_2d_array(nspecies, nmodes, qaer_delsub_cond, // in - qaer_delsub_grow4rnam); // out - - if(iscldy_subarea) { - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delsub_grow4rnam[is][im] = - (qaer3[is][im] - qaer2[is][im]) / ntsubstep + - qaer_delsub_grow4rnam[is][im]; - qaercw_delsub_grow4rnam[is][im] = - (qaercw3[is][im] - qaercw2[is][im]) / ntsubstep; - } - } - } - - //---------- - // Renaming - //---------- - copy_1d_array(nmodes, qnum_cur, // in - qnum_sv1); // out - - copy_2d_array(nspecies, nmodes, qaer_cur, // in - qaer_sv1); // out - - Real qnumcw_sv1[nmodes]; - copy_1d_array(nmodes, qnumcw_cur, // in - qnumcw_sv1); // out - Real qaercw_sv1[nspecies][nmodes]; - copy_2d_array(nspecies, nmodes, qaercw_cur, // in - qaercw_sv1); // out - - Real mean_std_dev[nmodes]; - Real fmode_dist_tail_fac[nmodes]; - Real v2n_lo_rlx[nmodes]; - Real v2n_hi_rlx[nmodes]; - Real ln_diameter_tail_fac[nmodes]; - int num_pairs = 0; - Real diameter_cutoff[nmodes]; - Real ln_dia_cutoff[nmodes]; - Real diameter_threshold[nmodes]; - - rename::find_renaming_pairs( - dest_mode_of_mode, // in - mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, // out - v2n_hi_rlx, ln_diameter_tail_fac, num_pairs, // out - diameter_cutoff, ln_dia_cutoff, // out - diameter_threshold); // out - Real dgnum_amode[nmodes]; - for(int m = 0; m < nmodes; ++m) { - dgnum_amode[m] = modes(m).nom_diameter; - } - // BAD_CONSTANT - constexpr Real smallest_dryvol_value = 1.0e-25; - - // swap dimensions as mam_rename_1subarea_ uses output arrays in - // a swapped dimension order - Real qaer_cur_tmp[nmodes][nspecies]; - Real qaer_delsub_grow4rnam_tmp[nmodes][nspecies]; - Real qaercw_cur_tmp[nmodes][nspecies]; - Real qaercw_delsub_grow4rnam_tmp[nmodes][nspecies]; - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_cur_tmp[im][is] = qaer_cur[is][im]; - qaer_delsub_grow4rnam_tmp[im][is] = qaer_delsub_grow4rnam[is][im]; - qaercw_cur_tmp[im][is] = qaercw_cur[is][im]; - qaercw_delsub_grow4rnam_tmp[im][is] = qaercw_delsub_grow4rnam[is][im]; - } - } - Rename rename; - rename.mam_rename_1subarea_( - iscldy_subarea, smallest_dryvol_value, dest_mode_of_mode, // in - mean_std_dev, fmode_dist_tail_fac, v2n_lo_rlx, v2n_hi_rlx, // in - ln_diameter_tail_fac, num_pairs, diameter_cutoff, // in - ln_dia_cutoff, diameter_threshold, mass_2_vol, dgnum_amode, // in - qnum_cur, qaer_cur_tmp, // out - qaer_delsub_grow4rnam_tmp, // in - qnumcw_cur, qaercw_cur_tmp, // out - qaercw_delsub_grow4rnam_tmp); // in - - // copy the output back to the variables - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_cur[is][im] = qaer_cur_tmp[im][is]; - qaer_delsub_grow4rnam[is][im] = qaer_delsub_grow4rnam_tmp[im][is]; - qaercw_cur[is][im] = qaercw_cur_tmp[im][is]; - qaercw_delsub_grow4rnam[is][im] = qaercw_delsub_grow4rnam_tmp[im][is]; - } - } - - //------------------------ - // Accumulate increments - //------------------------ - for(int im = 0; im < nmodes; ++im) { - qnum_delaa[im][iqtend_rnam()] = - qnum_delaa[im][iqtend_rnam()] + (qnum_cur[im] - qnum_sv1[im]); - } - - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delaa[is][im][iqtend_rnam()] = - qaer_delaa[is][im][iqtend_rnam()] + - (qaer_cur[is][im] - qaer_sv1[is][im]); - } - } - - if(iscldy_subarea) { - for(int im = 0; im < nmodes; ++im) { - qnumcw_delaa[im][iqqcwtend_rnam()] = - qnumcw_delaa[im][iqqcwtend_rnam()] + - (qnumcw_cur[im] - qnumcw_sv1[im]); - } - } // if iscldy_subarea - - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaercw_delaa[is][im][iqqcwtend_rnam()] = - qaercw_delaa[is][im][iqqcwtend_rnam()] + - (qaercw_cur[is][im] - qaercw_sv1[is][im]); - } - } - } // do_rename_sub - - //==================================== - // New particle formation (nucleation) - //==================================== - if(do_newnuc_sub) { - copy_1d_array(max_gas(), qgas_cur, // in - qgas_sv1); // out - copy_1d_array(nmodes, qnum_cur, // in - qnum_sv1); // out - - copy_2d_array(nspecies, nmodes, qaer_cur, // in - qaer_sv1); // out - - Real dnclusterdt_substep; - mam_newnuc_1subarea(igas_h2so4, gaexch_h2so4_uptake_optaa, - newnuc_h2so4_conc_optaa, jsubarea, dtsubstep, // in - temp, // in - pmid, aircon, zmid, pblh, // in - relhumsub, uptkrate_h2so4, del_h2so4_gasprod, // in - del_h2so4_aeruptk, // in - qgas_cur, qgas_avg, qnum_cur, qaer_cur, // out - dnclusterdt_substep); // out - - for(int ig = 0; ig < max_gas(); ++ig) { - qgas_delaa[ig][iqtend_nnuc()] += (qgas_cur[ig] - qgas_sv1[ig]); - } - for(int im = 0; im < nmodes; ++im) { - qnum_delaa[im][iqtend_nnuc()] += (qnum_cur[im] - qnum_sv1[im]); - } - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delaa[is][im][iqtend_nnuc()] += - (qaer_cur[is][im] - qaer_sv1[is][im]); - } - } - EKAT_KERNEL_ASSERT_MSG(deltat != 0, - "Error! mam_amicphys_1subarea: " - "deltat should not be equal to zero \n"); - ncluster_tend_nnuc_1grid += dnclusterdt_substep * (dtsubstep / deltat); - - } // do_newnuc_sub - - //==================================== - // Coagulation - //==================================== - if(do_coag_sub) { - copy_1d_array(nmodes, qnum_cur, // in - qnum_sv1); // out - - copy_2d_array(nspecies, nmodes, qaer_cur, // in - qaer_sv1); // out - - mam4::coagulation::mam_coag_1subarea( - dtsubstep, temp, pmid, aircon, // in - dgn_awet, wetdens, // in - qnum_cur, qaer_cur, qaer_delsub_coag_in); // inout, inout, out - - for(int im = 0; im < nmodes; ++im) { - qnum_delsub_coag[im] = qnum_cur[im] - qnum_sv1[im]; - } - - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delsub_coag[is][im] = qaer_cur[is][im] - qaer_sv1[is][im]; - } - } - - for(int im = 0; im < nmodes; ++im) { - qnum_delaa[im][iqtend_coag()] += qnum_delsub_coag[im]; - } - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delaa[is][im][iqtend_coag()] += qaer_delsub_coag[is][im]; - } - } - - } else { - assign_2d_array(nspecies, max_agepair, 0.0, // in - qaer_delsub_coag_in); // out - assign_2d_array(nspecies, nmodes, 0.0, // in - qaer_delsub_coag); // out - assign_1d_array(nmodes, 0.0, // in - qnum_delsub_coag); // out - - } // do_coag_sub - - //==================================== - // primary carbon aging - //==================================== - const bool do_aging_in_subarea = - (n_agepair() > 0) && - ((!iscldy_subarea) || (iscldy_subarea && do_cond_sub)); - - if(do_aging_in_subarea) { - mam4::aging::mam_pcarbon_aging_1subarea( - dgn_a, // input - qnum_cur, qnum_delsub_cond, qnum_delsub_coag, // in-outs - qaer_cur, qaer_delsub_cond, qaer_delsub_coag, // in-outs - qaer_delsub_coag_in); // in-outs - } // do_aging_in_subarea - - // The following block has to be placed here (after both condensation and - // aging) as both can change the values of qnum_delsub_cond and - // qaer_delsub_cond. - - if(do_cond_sub) { - for(int im = 0; im < nmodes; ++im) { - qnum_delaa[im][iqtend_cond()] = - qnum_delaa[im][iqtend_cond()] + qnum_delsub_cond[im]; - } - for(int is = 0; is < nspecies; ++is) { - for(int im = 0; im < nmodes; ++im) { - qaer_delaa[is][im][iqtend_cond()] = - qaer_delaa[is][im][iqtend_cond()] + qaer_delsub_cond[is][im]; - } - } - } // do_cond_sub - - } // jtsubstep_loop - -} // mam_amicphys_1subarea - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void mam_amicphys_1gridcell( - // in - const AmicPhysConfig &config, const Real deltat, const int nsubarea, - const int ncldy_subarea, const bool (&iscldy_subarea)[maxsubarea()], - const Real (&afracsub)[maxsubarea()], const Real temp, const Real pmid, - const Real pdel, const Real zmid, const Real pblh, - const Real (&relhumsub)[maxsubarea()], const Real (&dgn_a)[num_modes], - const Real (&dgn_awet)[num_modes], const Real (&wetdens)[num_modes], - const Real (&qsub1)[gas_pcnst][maxsubarea()], - const Real (&qsub2)[gas_pcnst][maxsubarea()], - const Real (&qqcwsub2)[gas_pcnst][maxsubarea()], - const Real (&qsub3)[gas_pcnst][maxsubarea()], - const Real (&qqcwsub3)[gas_pcnst][maxsubarea()], - const Real (&qaerwatsub3)[num_modes][maxsubarea()], - // out - Real (&qsub4)[gas_pcnst][maxsubarea()], - Real (&qqcwsub4)[gas_pcnst][maxsubarea()], - Real (&qaerwatsub4)[num_modes][maxsubarea()], - Real (&qsub_tendaa)[gas_pcnst][nqtendaa()][maxsubarea()], - Real (&qqcwsub_tendaa)[gas_pcnst][nqqcwtendaa()][maxsubarea()]) { - // - // calculates changes to gas and aerosol sub-area TMRs (tracer mixing - // ratios) qsub3 and qqcwsub3 are the incoming current TMRs qsub4 and - // qqcwsub4 are the outgoing updated TMRs - // - // qsubN and qqcwsubN (N=1:4) are tracer mixing ratios (TMRs, mol/mol or - // #/kmol) in sub-areas - // currently there are just clear and cloudy sub-areas - // the N=1:4 have same meanings as for qgcmN - // N=1 - before gas-phase chemistry - // N=2 - before cloud chemistry - // N=3 - incoming values (before gas-aerosol exchange, newnuc, coag) - // N=4 - outgoing values (after gas-aerosol exchange, newnuc, coag) - // qsub_tendaa and qqcwsub_tendaa are TMR tendencies - // for different processes, which are used to produce history output - // the processes are condensation/evaporation (and associated aging), - // renaming, coagulation, and nucleation - - constexpr int mdo_gaexch_cldy_subarea = 0; - // the qq--4 values will be equal to qq--3 values unless they get changed - for(int i = 0; i < num_gas_ids; ++i) { - for(int j = 1; j < maxsubarea(); ++j) { - qsub4[i][j] = qsub3[i][j]; - qqcwsub4[i][j] = qqcwsub3[i][j]; - } - } - - for(int i = 0; i < num_modes; ++i) { - for(int j = 0; j < maxsubarea(); ++j) { - qaerwatsub4[i][j] = qaerwatsub3[i][j]; - } - } - - assign_3d_array(num_gas_ids, nqtendaa(), maxsubarea(), 0.0, // in - qsub_tendaa); // out - - assign_3d_array(num_gas_ids, nqqcwtendaa(), maxsubarea(), 0.0, // in - qqcwsub_tendaa); // out - - EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), - "Error! mam_amicphys_1gridcell: " - "nsubarea should be < maxsubarea() \n"); - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - bool do_cond; - bool do_rename; - bool do_newnuc; - bool do_coag; - if(iscldy_subarea[jsub]) { - do_cond = config.do_cond; - do_rename = config.do_rename; - do_newnuc = false; - do_coag = false; - if(mdo_gaexch_cldy_subarea <= 0) do_cond = false; - } else { - do_cond = config.do_cond; - do_rename = config.do_rename; - do_newnuc = config.do_newnuc; - do_coag = config.do_coag; - } - const bool do_map_gas_sub = do_cond || do_newnuc; - - // map incoming sub-area mix-ratios to gas/aer/num arrays - Real qgas1[max_gas()] = {0}; - Real qgas2[max_gas()] = {0}; - Real qgas3[max_gas()] = {0}; - Real qgas4[max_gas()] = {0}; - assign_1d_array(max_gas(), 0.0, // in - qgas1); // out - assign_1d_array(max_gas(), 0.0, // in - qgas2); // out - assign_1d_array(max_gas(), 0.0, // in - qgas3); // out - assign_1d_array(max_gas(), 0.0, // in - qgas4); // out - - if(do_map_gas_sub) { - // for cldy subarea, only do gases if doing gaexch - for(int igas = 0; igas < max_gas(); ++igas) { - const int l = lmap_gas(igas); - qgas1[igas] = qsub1[l][jsub] * fcvt_gas(igas); - qgas2[igas] = qsub2[l][jsub] * fcvt_gas(igas); - qgas3[igas] = qsub3[l][jsub] * fcvt_gas(igas); - qgas4[igas] = qgas3[igas]; - } - } - - Real qaer2[num_aerosol_ids][num_modes] = {0}; - Real qnum2[num_modes] = {0}; - Real qaer3[num_aerosol_ids][num_modes] = {0}; - Real qnum3[num_modes] = {0}; - Real qaer4[num_aerosol_ids][num_modes] = {0}; - Real qnum4[num_modes] = {0}; - Real qwtr3[num_modes] = {0}; - Real qwtr4[num_modes] = {0}; - - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaer2); // out - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaer3); // out - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaer4); // out - - assign_1d_array(num_modes, 0.0, // in - qnum2); // out - assign_1d_array(num_modes, 0.0, // in - qnum3); // out - assign_1d_array(num_modes, 0.0, // in - qnum4); // out - assign_1d_array(num_modes, 0.0, // in - qwtr3); // out - assign_1d_array(num_modes, 0.0, // in - qwtr4); // out - - for(int imode = 0; imode < num_modes; ++imode) { - const int ln = lmap_num(imode); - qnum2[imode] = qsub2[ln][jsub] * fcvt_num(); - qnum3[imode] = qsub3[ln][jsub] * fcvt_num(); - qnum4[imode] = qnum3[imode]; - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - const int la = lmap_aer(iaer, imode); - if(la > 0) { - qaer2[iaer][imode] = qsub2[la][jsub] * fcvt_aer(iaer); - qaer3[iaer][imode] = qsub3[la][jsub] * fcvt_aer(iaer); - qaer4[iaer][imode] = qaer3[iaer][imode]; - } - } // for iaer - qwtr3[imode] = qaerwatsub3[imode][jsub] * fcvt_wtr(); - qwtr4[imode] = qwtr3[imode]; - } // for imode - - Real qaercw2[num_aerosol_ids][num_modes] = {0}; - Real qnumcw2[num_modes] = {0}; - Real qaercw3[num_aerosol_ids][num_modes] = {0}; - Real qnumcw3[num_modes] = {0}; - Real qaercw4[num_aerosol_ids][num_modes] = {0}; - Real qnumcw4[num_modes] = {0}; - - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaercw2); // out - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaercw3); // out - assign_2d_array(num_aerosol_ids, num_modes, 0.0, // in - qaercw4); // out - - assign_1d_array(num_modes, 0.0, // in - qnumcw2); // out - assign_1d_array(num_modes, 0.0, // in - qnumcw3); // out - assign_1d_array(num_modes, 0.0, // in - qnumcw4); // out - - if(iscldy_subarea[jsub]) { - for(int imode = 0; imode < num_modes; ++imode) { - qnumcw2[imode] = 0; - qnumcw3[imode] = 0; - qnumcw4[imode] = 0; - } // imode - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - for(int imode = 0; imode < num_modes; ++imode) { - qaercw2[iaer][imode] = 0; - qaercw3[iaer][imode] = 0; - qaercw4[iaer][imode] = 0; - } // imode - } // iaer - // only do cloud-borne for cloudy - for(int imode = 0; imode < num_modes; ++imode) { - int ln = lmap_numcw(imode); - qnumcw2[imode] = qqcwsub2[ln][jsub] * fcvt_num(); - qnumcw3[imode] = qqcwsub3[ln][jsub] * fcvt_num(); - qnumcw4[imode] = qnumcw3[imode]; - } // imode - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - for(int imode = 0; imode < num_modes; ++imode) { - int la = lmap_aer(iaer, imode); - if(la > 0) { - qaercw2[iaer][imode] = qqcwsub2[la][jsub] * fcvt_aer(iaer); - qaercw3[iaer][imode] = qqcwsub3[la][jsub] * fcvt_aer(iaer); - qaercw4[iaer][imode] = qaercw3[iaer][imode]; - } // la - } // imode - } // iaer - } // iscldy_subarea - - Real qgas_delaa[max_gas()][nqtendaa()] = {}; - Real qnum_delaa[num_modes][nqtendaa()] = {}; - Real qnumcw_delaa[num_modes][nqqcwtendaa()] = {}; - Real qaer_delaa[num_aerosol_ids][num_modes][nqtendaa()] = {}; - Real qaercw_delaa[num_aerosol_ids][num_modes][nqqcwtendaa()] = {}; - - mam_amicphys_1subarea( - // in - config.gaexch_h2so4_uptake_optaa, config.newnuc_h2so4_conc_optaa, - do_cond, do_rename, do_newnuc, do_coag, deltat, jsub, - iscldy_subarea[jsub], afracsub[jsub], temp, pmid, pdel, zmid, pblh, - relhumsub[jsub], dgn_a, dgn_awet, wetdens, qgas1, qgas3, qgas4, - qgas_delaa, // out - qnum3, // in - qnum4, qnum_delaa, // out - qaer2, qaer3, // in - qaer4, qaer_delaa, // out - qwtr3, // in - qwtr4, // out - qnumcw3, // in - qnumcw4, qnumcw_delaa, // out - qaercw2, qaercw3, // in - qaercw4, qaercw_delaa); // out - - // FIXME: Enable this functionality - /*if (nsubarea == 1 || !iscldy_subarea[jsub]) { - ncluster_tend_nnuc_1grid = ncluster_tend_nnuc_1grid & - + - misc_vars_aa_sub(jsub)%ncluster_tend_nnuc_1grid*afracsub(jsub) - }*/ - - // map gas/aer/num arrays (mix-ratio and del=change) back to sub-area arrays - - if(do_map_gas_sub) { - for(int igas = 0; igas < max_gas(); ++igas) { - int ll = lmap_gas(igas); - qsub4[ll][jsub] = qgas4[igas] / fcvt_gas(igas); - for(int jj = 0; jj < nqtendaa(); ++jj) { - qsub_tendaa[ll][jj][jsub] = - qgas_delaa[igas][jj] / (fcvt_gas(igas) * deltat); - } - } // igas - } // do_map_gas_sub - - for(int imode = 0; imode < num_modes; ++imode) { - int ll = lmap_num(imode); - qsub4[ll][jsub] = qnum4[imode] / (fcvt_num()); - for(int jj = 0; jj < nqtendaa(); ++jj) { - qsub_tendaa[ll][jj][jsub] = - qnum_delaa[imode][jj] / (fcvt_num() * deltat); - } - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - int la = lmap_aer(iaer, imode); - if(la > 0) { - qsub4[la][jsub] = qaer4[iaer][imode] / fcvt_aer(iaer); - for(int jj = 0; jj < nqtendaa(); ++jj) { - qsub_tendaa[la][jj][jsub] = - qaer_delaa[iaer][imode][jj] / (fcvt_aer(iaer) * deltat); - } // jj - } // la - } // iaer - qaerwatsub4[imode][jsub] = qwtr4[imode] / fcvt_wtr(); - - if(iscldy_subarea[jsub]) { - int lc = lmap_numcw(imode); - qqcwsub4[lc][jsub] = qnumcw4[imode] / fcvt_num(); - for(int jj = 0; jj < nqqcwtendaa(); ++jj) { - qqcwsub_tendaa[lc][jj][jsub] = - qnumcw_delaa[imode][jj] / (fcvt_num() * deltat); - } // jj - for(int iaer = 0; iaer < num_aerosol_ids; ++iaer) { - int lca = lmap_aercw(iaer, imode); - if(lca > 0) { - qqcwsub4[lca][jsub] = qaercw4[iaer][imode] / fcvt_aer(iaer); - for(int jj = 0; jj < nqqcwtendaa(); ++jj) { - qqcwsub_tendaa[lca][jj][jsub] = - qaercw_delaa[iaer][imode][jj] / (fcvt_aer(iaer) * deltat); - } // jj - } // lca - } // iaer - } // iscldy_subarea - } // imode - } // main_jsub_loop - -} // mam_amicphys_1gridcell - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void form_gcm_of_gases_and_aerosols_from_subareas( - // in - const int nsubarea, const int ncldy_subarea, - const Real (&afracsub)[maxsubarea()], - const Real (&qsub)[gas_pcnst][maxsubarea()], - const Real (&qqcwsub)[gas_pcnst][maxsubarea()], - const Real (&qqcwgcm_old)[gas_pcnst], - // out - Real (&qgcm)[gas_pcnst], Real (&qqcwgcm)[gas_pcnst]) { - //-------------------------------------------------------------------------- - // Purpose: Form grid cell mean values by calculating area-weighted averages - // of the subareas. - // - For gases and interstitial aerosols, sum over all active - // subareas. - // - For cloud-borne aerosols, - //--------------------------------------------------------------------------- - - // nsubarea: # of active subareas - // ncldy_subarea : # of cloudy subareas - // afracsub(maxsubarea):area fraction of subareas [unitless] - - // The following arguments are mixing ratios. Their units do not matter for - // this subroutine. - - // qsub (ncnst, maxsubarea):gas and interst. aerosol mixing ratios in - // subareas - // qqcwsub(ncnst, maxsubarea): cloud-borne aerosol mixing ratios in - // subareas - // qqcwgcm_old(ncnst): grid cell mean cloud-borne aerosol mixing - // ratios before aerosol microphysics calculations - // qgcm (ncnst): grid cell mean gas and interst. aerosol mixing ratios - // qqcwgcm(ncnst): cloud-borne aerosol mixing ratios in subareas - - // Gases and interstitial aerosols - assign_1d_array(gas_pcnst, 0.0, // in - qgcm); // out - - EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), - "Error! form_gcm_of_gases_and_aerosols_from_subareas: " - "nsubarea should be < maxsubarea() \n"); - - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qgcm[icnst] += qsub[icnst][jsub] * afracsub[jsub]; - } - } - - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qgcm[icnst] = haero::max(0, qgcm[icnst]); - } - - // Cloud-borne aerosols - if(ncldy_subarea <= 0) { - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qqcwgcm[icnst] = qqcwgcm_old[icnst]; - } - } else { - assign_1d_array(gas_pcnst, 0.0, // in - qqcwgcm); // out - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qqcwgcm[icnst] += qqcwsub[icnst][jsub] * afracsub[jsub]; - } - } - } // if ncldy_subarea -} // form_gcm_of_gases_and_aerosols_from_subareas - -//-------------------------------------------------------------------------------- -// Purpose: Get grid cell mean tendencies by calculating area-weighted averages -// of the values in different subareas. -//-------------------------------------------------------------------------------- -KOKKOS_INLINE_FUNCTION -void get_gcm_tend_diags_from_subareas( - // in - const int nsubarea, const int ncldy_subarea, - const Real (&afracsub)[maxsubarea()], - const Real (&qsub_tendaa)[gas_pcnst][nqtendaa()][maxsubarea()], - const Real (&qqcwsub_tendaa)[gas_pcnst][nqqcwtendaa()][maxsubarea()], - // out - Real (&qgcm_tendaa)[gas_pcnst][nqtendaa()], - Real (&qqcwgcm_tendaa)[gas_pcnst][nqqcwtendaa()]) { - // nsubarea: # of active subareas - // ncldy_subarea: # of cloudy subareas - // afracsub(maxsubarea): area fraction of subareas [unitless] - - // Gases and interstitial aerosols - assign_2d_array(gas_pcnst, nqtendaa(), 0.0, // in - qgcm_tendaa); // out - - EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), - "Error! get_gcm_tend_diags_from_subareas: " - "nsubarea should be < maxsubarea() \n"); - - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - for(int iq = 0; iq < nqtendaa(); ++iq) { - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qgcm_tendaa[icnst][iq] += qsub_tendaa[icnst][iq][jsub] * afracsub[jsub]; - } - } - } - - // Cloud-borne aerosols - - assign_2d_array(gas_pcnst, nqqcwtendaa(), 0.0, // in - qqcwgcm_tendaa); // out - if(ncldy_subarea > 0) { - for(int jsub = 1; jsub <= nsubarea; ++jsub) { - for(int iq = 0; iq < nqqcwtendaa(); ++iq) { - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - qqcwgcm_tendaa[icnst][iq] += - qqcwsub_tendaa[icnst][iq][jsub] * afracsub[jsub]; - } - } - } - } // if (ncldy_subarea -} // get_gcm_tend_diags_from_subareas - -} // anonymous namespace - -//-------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------- - -KOKKOS_INLINE_FUNCTION -void modal_aero_amicphys_intr( - // in - const AmicPhysConfig &config, const Real deltat, const Real temp, - const Real pmid, const Real pdel, const Real zm, const Real pblh, - const Real qv, const Real cld, - // out - Real qq[gas_pcnst], Real qqcw[gas_pcnst], - // in - const Real (&q_pregaschem)[gas_pcnst], - const Real (&q_precldchem)[gas_pcnst], - const Real (&qqcw_precldchem)[gas_pcnst], const Real (&dgncur_a)[num_modes], - const Real (&dgncur_awet)[num_modes], - const Real (&wetdens_host)[num_modes]) { - // deltat: time step - // qq(ncol,pver,pcnst): current tracer mixing ratios (TMRs) - // these values are updated (so out /= in) - // *** MUST BE #/kmol-air for number - // *** MUST BE mol/mol-air for mass - // *** NOTE ncol dimension - // qqcw(ncol,pver,pcnst): - // like qq but for cloud-borner tracers - // these values are updated - // q_pregaschem(ncol,pver,pcnst): qq TMRs before gas-phase - // chemistry - // - // q_precldchem(ncol,pver,pcnstxx): qq TMRs before cloud - // chemistry - // qqcw_precldchem(ncol,pver,pcnstxx): qqcw TMRs before cloud - // chemistry - // t(pcols,pver): temperature at model levels (K) - // pmid(pcols,pver):pressure at model level centers (Pa) - // pdel(pcols,pver):pressure thickness of levels (Pa) - // zm(pcols,pver) :altitude (above ground) at level centers (m) - // pblh(pcols) :planetary boundary layer depth (m) - // qv(pcols,pver) :specific humidity (kg/kg) - // cld(ncol,pver) :cloud fraction (-) *** NOTE ncol dimension - // dgncur_a(pcols,pver,ntot_amode) - // dgncur_awet(pcols,pver,ntot_amode) - // :dry & wet geo. mean dia. (m) of - // number distrib. wetdens_host(pcols,pver,ntot_amode): interstitial - // aerosol wet density (kg/m3) - - // DESCRIPTION: - // calculates changes to gas and aerosol TMRs (tracer mixing ratios) from - // gas-aerosol exchange (condensation/evaporation) - // growth from smaller to larger modes (renaming) due to both - // condensation and cloud chemistry - // new particle nucleation - // coagulation - // transfer of particles from hydrophobic modes to hydrophilic modes - // (aging) - // due to condensation and coagulation - // - // the incoming mixing ratios (qq and qqcw) are updated before output - // - // REVISION HISTORY: - // RCE 07.04.13: Adapted from earlier version of CAM5 modal aerosol - // routines - // for these processes - // - - // qgcmN and qqcwgcmN (N=1:4) are grid-cell mean tracer mixing ratios - // (TMRs, mol/mol or #/kmol) - // N=1 - before gas-phase chemistry - // N=2 - before cloud chemistry - // N=3 - incoming values (before gas-aerosol exchange, newnuc, coag) - // N=4 - outgoing values (after gas-aerosol exchange, newnuc, coag) - - // qsubN and qqcwsubN (N=1:4) are TMRs in sub-areas - // currently there are just clear and cloudy sub-areas - // the N=1:4 have same meanings as for qgcmN - - // Compute saturation vapor pressure - const Real epsqs = haero::Constants::weight_ratio_h2o_air; - - // Saturation vapor pressure - const Real ev_sat = conversions::vapor_saturation_pressure_magnus(temp, pmid); - - // Saturation specific humidity - const Real qv_sat = epsqs * ev_sat / (pmid - (1 - epsqs) * ev_sat); - - // total # of subareas to do calculations for - int nsubarea; - - // total # of cloudy subareas - int ncldy_subarea; - - // indices of the clear and cloudy subareas - int jclea, jcldy; - - // whether a subarea is cloudy - bool iscldy_subarea[maxsubarea()]; - - // area fraction of each active subarea[unitless] - Real afracsub[maxsubarea()]; - - // cloudy and clear fractions of the grid cell - Real fcldy, fclea; - - setup_subareas(cld, // in - nsubarea, ncldy_subarea, jclea, jcldy, // out - iscldy_subarea, afracsub, fclea, fcldy); // out - EKAT_KERNEL_ASSERT_MSG(nsubarea < maxsubarea(), - "Error! modal_aero_amicphys_intr: " - "nsubarea should be < maxsubarea() \n"); - - const Real relhumgcm = haero::max(0.0, haero::min(1.0, qv / qv_sat)); - - Real relhumsub[maxsubarea()]; - set_subarea_rh(ncldy_subarea, jclea, jcldy, afracsub, relhumgcm, // in - relhumsub); // out - - //------------------------------- - // Set aerosol water in subareas - //------------------------------- - // Notes from Dick Easter/Steve Ghan: how to treat aerosol water in - // subareas needs more work/thinking Currently modal_aero_water_uptake - // calculates qaerwat using the grid-cell mean interstital-aerosol - // mix-rates and the clear-area RH. aerosol water mixing ratios (mol/mol) - Real qaerwatsub3[num_modes][maxsubarea()]; - assign_2d_array(num_modes, maxsubarea(), 0.0, // in - qaerwatsub3); // out - - //------------------------------------------------------------------------- - // Set gases, interstitial aerosols, and cloud-borne aerosols in subareas - //------------------------------------------------------------------------- - // Copy grid cell mean mixing ratios; clip negative values if any. - Real qgcm1[gas_pcnst], qgcm2[gas_pcnst], qgcm3[gas_pcnst]; - Real qqcwgcm2[gas_pcnst], qqcwgcm3[gas_pcnst]; // cld borne aerosols - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - // Gases and interstitial aerosols - qgcm1[icnst] = haero::max(0, q_pregaschem[icnst]); - qgcm2[icnst] = haero::max(0, q_precldchem[icnst]); - qgcm3[icnst] = haero::max(0, qq[icnst]); - - // Cloud-borne aerosols - qqcwgcm2[icnst] = haero::max(0, qqcw_precldchem[icnst]); - qqcwgcm3[icnst] = haero::max(0, qqcw[icnst]); - } - // Partition grid cell mean to subareas - Real qsub1[gas_pcnst][maxsubarea()]; - Real qsub2[gas_pcnst][maxsubarea()]; - Real qsub3[gas_pcnst][maxsubarea()]; - Real qqcwsub1[gas_pcnst][maxsubarea()]; - Real qqcwsub2[gas_pcnst][maxsubarea()]; - Real qqcwsub3[gas_pcnst][maxsubarea()]; - - set_subarea_gases_and_aerosols(nsubarea, jclea, jcldy, fclea, // in - fcldy, // in - qgcm1, qgcm2, qqcwgcm2, qgcm3, // in - qqcwgcm3, // in - qsub1, qsub2, qqcwsub2, qsub3, // out - qqcwsub3); // out - - // Initialize the "after-amicphys" values - Real qsub4[gas_pcnst][maxsubarea()] = {}; - Real qqcwsub4[gas_pcnst][maxsubarea()] = {}; - Real qaerwatsub4[num_modes][maxsubarea()] = {}; - - // - // start integration - // - Real dgn_a[num_modes] = {}; - Real dgn_awet[num_modes] = {}; - Real wetdens[num_modes] = {}; - assign_1d_array(num_modes, 0.0, // in - dgn_a); // out - assign_1d_array(num_modes, 0.0, // in - dgn_awet); // out - assign_1d_array(num_modes, 1000.0, // in - wetdens); // out - - for(int imode = 0; imode < num_modes; ++imode) { - dgn_a[imode] = dgncur_a[imode]; - dgn_awet[imode] = dgncur_awet[imode]; - wetdens[imode] = haero::max(1000.0, wetdens_host[imode]); - } - - Real qsub_tendaa[gas_pcnst][nqtendaa()][maxsubarea()] = {}; - Real qqcwsub_tendaa[gas_pcnst][nqqcwtendaa()][maxsubarea()] = {}; - mam_amicphys_1gridcell( - // in - config, deltat, nsubarea, ncldy_subarea, iscldy_subarea, afracsub, temp, - pmid, pdel, zm, pblh, relhumsub, dgn_a, dgn_awet, wetdens, qsub1, qsub2, - qqcwsub2, qsub3, qqcwsub3, - // inout - qaerwatsub3, qsub4, qqcwsub4, qaerwatsub4, qsub_tendaa, qqcwsub_tendaa); - - //================================================================================================= - // Aerosol microphysics calculations done for all subareas. Form new grid cell - // mean mixing ratios. - //================================================================================================= - // Gases and aerosols - //---------------------- - // Calculate new grid cell mean values - Real qgcm4[gas_pcnst]; - Real qqcwgcm4[gas_pcnst]; - - form_gcm_of_gases_and_aerosols_from_subareas( - // in - nsubarea, ncldy_subarea, afracsub, qsub4, qqcwsub4, qqcwgcm3, - // out - qgcm4, qqcwgcm4); - - // Copy grid cell mean values to output arrays - for(int icnst = 0; icnst < gas_pcnst; ++icnst) { - if(lmapcc_all(icnst) > 0) { - qq[icnst] = qgcm4[icnst]; - } - if(lmapcc_all(icnst) >= lmapcc_val_aer()) { - qqcw[icnst] = qqcwgcm4[icnst]; - } - } - - //================================================================ - // Process diagnostics of the current grid cell - //================================================================ - Real qgcm_tendaa[gas_pcnst][nqtendaa()]; - Real qqcwgcm_tendaa[gas_pcnst][nqqcwtendaa()]; - get_gcm_tend_diags_from_subareas( - // in - nsubarea, ncldy_subarea, afracsub, qsub_tendaa, qqcwsub_tendaa, - // out - qgcm_tendaa, qqcwgcm_tendaa); - -#if 0 - //This code is for diagnostics only - // Get gravity - using C = physics::Constants; - static constexpr auto gravit = C::gravit; // Gravity [m/s2] - -accumulate_column_tend_integrals( pdel, gravit, // in - qgcm_tendaa, qqcwgcm_tendaa, // in - q_coltendaa(ii,:,:), qqcw_coltendaa(ii,:,:) )// inout - -ncluster_3dtend_nnuc(ii,kk) = misc_vars_aa%ncluster_tend_nnuc_1grid - -#endif -} // modal_aero_amicphys_intr - -} // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp b/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp similarity index 59% rename from components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp rename to components/eamxx/src/physics/mam/impl/photo_table_utils.cpp index d977340b995..67d8ea761ef 100644 --- a/components/eamxx/src/physics/mam/impl/gas_phase_chemistry.cpp +++ b/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp @@ -2,33 +2,8 @@ namespace scream::impl { -using mam4::utils::min_max_bound; - -// The following variables are chemistry mechanism dependent! See -// mam4xx/src/mam4xx/gas_chem_mechanism.hpp) - -// number of gases+aerosols species -using mam4::gas_chemistry::gas_pcnst; -// number of species with external forcing -using mam4::gas_chemistry::extcnt; -// index of total atm density in invariant array -using mam4::gas_chemistry::indexm; -// number of chemical reactions -using mam4::gas_chemistry::rxntot; // number of photolysis reactions using mam4::mo_photo::phtcnt; -// number of invariants -using mam4::gas_chemistry::nfs; - -// FIXME: Hardwired indices, should be obtained from a config file -constexpr int ndx_h2so4 = 2; -constexpr int synoz_ndx = -1; - -// Following (indices (ndxes?) are taken from mam4 validation data and -// translated from -// 1-based indices to 0-based indices) -constexpr int usr_HO2_HO2_ndx = 1, usr_DMS_OH_ndx = 5, usr_SO2_OH_ndx = 3, - inv_h2o_ndx = 3; using HostView1D = haero::DeviceType::view_1d::HostMirror; using HostViewInt1D = haero::DeviceType::view_1d::HostMirror; @@ -66,7 +41,7 @@ mam4::mo_photo::PhotoTableData read_photo_table( const std::string &rsf_file, const std::string &xs_long_file) { // set up the lng_indexer and pht_alias_mult_1 views based on our // (hardwired) chemical mechanism - HostViewInt1D lng_indexer_h("lng_indexer(host)", mam4::mo_photo::phtcnt); + HostViewInt1D lng_indexer_h("lng_indexer(host)", phtcnt); int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions scorpio::register_file(rsf_file, scorpio::Read); @@ -112,7 +87,7 @@ mam4::mo_photo::PhotoTableData read_photo_table( // read xsqy data (using lng_indexer_h for the first index) // FIXME: hard-coded for only one photo reaction. - for(int m = 0; m < mam4::mo_photo::phtcnt; ++m) { + for(int m = 0; m < phtcnt; ++m) { auto xsqy_ndx_h = ekat::subview(xsqy_h, m); scorpio::read_var(xs_long_file, rxt_names[m], xsqy_h.data()); } @@ -173,94 +148,4 @@ mam4::mo_photo::PhotoTableData read_photo_table( return table; } -// ================================================================ -// Gas Phase Chemistry -// ================================================================ - -// performs gas phase chemistry calculations on a single level of a single -// atmospheric column -KOKKOS_INLINE_FUNCTION -void gas_phase_chemistry( - // in - const Real temp, const Real dt, - const Real photo_rates[mam4::mo_photo::phtcnt], const Real extfrc[extcnt], - const Real invariants[nfs], const int (&clsmap_4)[gas_pcnst], - const int (&permute_4)[gas_pcnst], - // out - Real (&qq)[gas_pcnst], Real (&vmr0)[gas_pcnst]) { - //===================================================================== - // ... set rates for "tabular" and user specified reactions - //===================================================================== - Real reaction_rates[rxntot]; - mam4::gas_chemistry::setrxt(reaction_rates, // out - temp); // in - - // set reaction rates based on chemical invariants - mam4::gas_chemistry::usrrxt(reaction_rates, // out - temp, invariants, invariants[indexm], // in - usr_HO2_HO2_ndx, usr_DMS_OH_ndx, // in - usr_SO2_OH_ndx, // in - inv_h2o_ndx); // in - - mam4::gas_chemistry::adjrxt(reaction_rates, // out - invariants, invariants[indexm]); // in - - //=================================== - // Photolysis rates at time = t(n+1) - //=================================== - - // compute the rate of change from forcing - Real extfrc_rates[extcnt]; // [1/cm^3/s] - for(int mm = 0; mm < extcnt; ++mm) { - if(mm != synoz_ndx) { - extfrc_rates[mm] = extfrc[mm] / invariants[indexm]; - } - } - - // ... Form the washout rates - // FIXME: het_rates will be provided by sethet routine to be called before - // the vertical level parallel_for in the interface - Real het_rates[gas_pcnst] = {0}; - // save h2so4 before gas phase chem (for later new particle nucleation) - Real del_h2so4_gasprod = qq[ndx_h2so4]; - - //=========================== - // Class solution algorithms - //=========================== - - // copy photolysis rates into reaction_rates (assumes photolysis rates come - // first) - for(int i = 0; i < phtcnt; ++i) { - reaction_rates[i] = photo_rates[i]; - } - - // ... solve for "Implicit" species - using mam4::gas_chemistry::itermax; - bool factor[itermax]; - for(int i = 0; i < itermax; ++i) { - factor[i] = true; - } - - // initialize error tolerances - using mam4::gas_chemistry::clscnt4; - Real epsilon[clscnt4]; - mam4::gas_chemistry::imp_slv_inti(epsilon); - - // Mixing ratios before chemistry changes - for(int i = 0; i < gas_pcnst; ++i) { - vmr0[i] = qq[i]; - } - - // solve chemical system implicitly - Real prod_out[clscnt4], loss_out[clscnt4]; - mam4::gas_chemistry::imp_sol(qq, reaction_rates, het_rates, extfrc_rates, dt, - permute_4, clsmap_4, factor, epsilon, prod_out, - loss_out); - - // save h2so4 change by gas phase chem (for later new particle nucleation) - if(ndx_h2so4 > 0) { - del_h2so4_gasprod = qq[ndx_h2so4] - del_h2so4_gasprod; - } -} - } // namespace scream::impl diff --git a/components/eamxx/src/physics/mam/mam_coupling.hpp b/components/eamxx/src/physics/mam/mam_coupling.hpp index 4e116fc5678..68f67f62c5c 100644 --- a/components/eamxx/src/physics/mam/mam_coupling.hpp +++ b/components/eamxx/src/physics/mam/mam_coupling.hpp @@ -810,26 +810,6 @@ void copy_view_lev_slice(haero::ThreadTeamPolicy team_policy, //inputs }); }); } -// FIXME: check if we have ported these function in mam4xx. If no, let's move them there. -KOKKOS_INLINE_FUNCTION -void mmr2vmr(const Real q[gas_pcnst()],//in - const Real mw[gas_pcnst()],//in - Real vmr[gas_pcnst()])// out -{ -for (int i = 0; i < gas_pcnst(); ++i) { - vmr[i] = mam4::conversions::vmr_from_mmr(q[i], mw[i]); -} -}//mmr2vmr -// FIXME: check if we have ported these function in mam4xx. If no, let's move there. -KOKKOS_INLINE_FUNCTION -void vmr2mmr(const Real vmr[gas_pcnst()], //in - const Real mw[gas_pcnst()], // in - Real q[gas_pcnst()]) // out -{ -for (int i = 0; i < gas_pcnst(); ++i) { - q[i] = mam4::conversions::mmr_from_vmr(vmr[i], mw[i]); -} -}//mmr2vmr } // namespace scream::mam_coupling diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index fe167ed67a7..44bb9ee687e 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -10,6 +10,15 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] + mam4_aero_microphys: + mam4_do_cond: true + mam4_do_newnuc: true + mam4_do_coag: true + mam4_do_rename: true + mam4_o3_tau: 172800.0 + mam4_o3_sfc: 3.0E-008 + mam4_o3_lbl: 4 + mam4_aero_microphys: mam4_linoz_ymd : 20100101 mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc @@ -46,11 +55,10 @@ initial_conditions: topography_filename: ${TOPO_DATA_DIR}/${EAMxx_tests_TOPO_FILE} phis : 1.0 pbl_height: 1.0 - dgncur_a : [1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08] - dgnumwet : [1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08] - qaerwat : [1193.43 ,1188.03 ,1665.08 ,1044.58] - wetdens : [5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22] #These should come from the input file + dgnum: [1.246662106183775E-007, 4.081134799487888E-008, 1.103139143795796E-006, 1.000000011686097E-007] + dgnumwet: [2.367209731605067E-007, 6.780643470563889E-008, 3.028011448344027E-006, 1.000000096285154E-007] + wetdens: [1038.67760516297, 1046.20002003441, 1031.74623165457, 1086.79731859184] # The parameters for I/O control Scorpio: diff --git a/externals/mam4xx b/externals/mam4xx index 4b429ed3dde..d2a862dfc90 160000 --- a/externals/mam4xx +++ b/externals/mam4xx @@ -1 +1 @@ -Subproject commit 4b429ed3dde2c3c56bed64674d908b9cd323f2a7 +Subproject commit d2a862dfc909ca782afd86ab14fdbc248403aba4 From c45f0346b91f003fe01f21286652ffc849f1f8ad Mon Sep 17 00:00:00 2001 From: Balwinder Singh Date: Mon, 21 Oct 2024 22:38:09 -0700 Subject: [PATCH 23/31] Addresses review comments: adds doc strings, reverts scorpio file change and cleanup Renaming the helper_micro.hpp file for clarity. Correcting the inclusion of helper_micro. Removing README.md because we transferred the mam4 routines to mam4xx. Removing std::vector from the initialization of extfrc_lst_. Move EKAT_REQUIRE_MSG outside of for-loops. Using extent_int. Using MDRangePolicy instead of team policy. Update components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp Co-authored-by: Naser Mahfouz Only compile single-process test with double precision. Updating the name of a field in output.yaml. This PR renames the field dgncur_a to dgnum in eamxx_mam_wetscav_process_interface, which requires changes in the output.yaml files. Due to this change, we need to regenerate the baseline files for all tests where the wet_scav process is involved if dgncur_a is listed in output.yaml. Using double to get parameter from EKAT parser. Adds mapping files for finer resolutions Refactoring: Renamed compute_days function for better clarity and added detailed comments. Adding modification from PR #3063. --- .../cime_config/namelist_defaults_scream.xml | 74 +++---- .../mam/eamxx_mam_aci_process_interface.cpp | 6 - ...mxx_mam_microphysics_process_interface.cpp | 182 ++++++++---------- ...mxx_mam_microphysics_process_interface.hpp | 7 +- .../eamxx_mam_wetscav_process_interface.cpp | 4 +- .../eamxx/src/physics/mam/impl/README.md | 11 -- .../physics/mam/impl/photo_table_utils.cpp | 2 +- ...lper_micro.hpp => tracer_reader_utils.hpp} | 55 +++--- .../src/share/io/scream_scorpio_interface.cpp | 4 +- .../output.yaml | 2 +- .../mam/p3_mam4_wetscav/output.yaml | 2 +- .../mam/shoc_cldfrac_p3_wetscav/output.yaml | 2 +- .../single-process/mam/wet_scav/output.yaml | 2 +- externals/mam4xx | 2 +- 14 files changed, 156 insertions(+), 199 deletions(-) delete mode 100644 components/eamxx/src/physics/mam/impl/README.md rename components/eamxx/src/physics/mam/impl/{helper_micro.hpp => tracer_reader_utils.hpp} (94%) diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 1b58c6ed5fc..70d771e0440 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -272,49 +272,57 @@ be lost if SCREAM_HACK_XML is not enabled. - true - true - true - true + true + true + true + true - 172800.0 - 3.0E-008 - 4 + 172800.0 + 3.0E-008 + 4 20100101 - ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne30pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne30pg2_c20240724.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne30pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne30pg2_c20240724.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc 20150101 - ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne30pg2/oxid_1.9x2.5_L26_1850-2015_ne30pg2_c20241009.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne4pg2/oxid_1.9x2.5_L26_1850-2015_ne4pg2_c20241009.nc - 20100101 - ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne30pg2/oxid_1.9x2.5_L26_1850-2015_ne30pg2_c20241009.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/invariants/ne4pg2/oxid_1.9x2.5_L26_1850-2015_ne4pg2_c20241009.nc + 20100101 + ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc 20100101 - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne30pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne30pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc - ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_so4_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_pom_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_bc_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a1_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a2_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_num_a4_elev_1x1_2010_clim_ne4pg2_c20241008.nc + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/elevated/cmip6_mam4_soag_elev_1x1_2010_clim_ne4pg2_c20241008.nc + + + + ${DIN_LOC_ROOT}/atm/scream/maps/map_ne30pg2_to_ne120pg2_20231201.nc + ${DIN_LOC_ROOT}/atm/scream/maps/map_ne30pg2_to_ne256pg2_20231201.nc + ${DIN_LOC_ROOT}/atm/scream/maps/map_ne30pg2_to_ne512pg2_20231201.nc + ${DIN_LOC_ROOT}/atm/scream/maps/map_ne30pg2_to_ne1024pg2_20231201.nc + diff --git a/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp index c7ad993e974..449ba4a55d8 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_aci_process_interface.cpp @@ -74,12 +74,6 @@ void MAMAci::set_grids( // layout for 2D (1d horiz X 1d vertical) variable FieldLayout scalar2d_layout_col{{COL}, {ncol_}}; - auto make_layout = [](const std::vector &extents, - const std::vector &names) { - std::vector tags(extents.size(), CMP); - return FieldLayout(tags, extents, names); - }; - using namespace ekat::units; constexpr auto q_unit = kg / kg; // units of mass mixing ratios of tracers constexpr auto n_unit = 1 / kg; // units of number mixing ratios of tracers diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index bf5678113d4..a51fa1c1ce3 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -21,46 +21,6 @@ namespace scream { MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, const ekat::ParameterList ¶ms) : AtmosphereProcess(comm, params), aero_config_() { - // Asserts for the runtime or namelist options - // ----- Aerosol Microphysics processes on/off switches ------- - EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_cond"), - "ERROR: mam4_do_cond is missing from mam4_aero_microphys " - "parameter list."); - - EKAT_REQUIRE_MSG( - m_params.isParameter("mam4_do_rename"), - "ERROR: mam4_do_rename is missing from mam4_aero_microphys " - "parameter list."); - - EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_newnuc"), - "ERROR: mam4_do_newnuc is missing from mam4_aero_microphys " - " parameter list."); - - EKAT_REQUIRE_MSG(m_params.isParameter("mam4_do_coag"), - "ERROR: mam4_do_coag is missing from mam4_aero_microphys " - "parameter list."); - // ----- LINOZ namelist parameters ------- - - EKAT_REQUIRE_MSG( - m_params.isParameter("mam4_o3_tau"), - "ERROR: mam4_o3_tau is missing from mam4_aero_microphys parameter list."); - - EKAT_REQUIRE_MSG(m_params.isParameter("mam4_o3_sfc"), - "ERROR: mam4_o3_sfc is missing from mam4_aero_microphys " - "parameter list."); - - EKAT_REQUIRE_MSG(m_params.isParameter("mam4_o3_lbl"), - "ERROR: mam4_o3_lbl is missing from mam4_aero_microphys " - "parameter list."); - - set_namelist_params_(); -} - -AtmosphereProcessType MAMMicrophysics::type() const { - return AtmosphereProcessType::Physics; -} - -void MAMMicrophysics::set_namelist_params_() { config_.amicphys.do_cond = m_params.get("mam4_do_cond"); config_.amicphys.do_rename = m_params.get("mam4_do_rename"); config_.amicphys.do_newnuc = m_params.get("mam4_do_newnuc"); @@ -69,13 +29,27 @@ void MAMMicrophysics::set_namelist_params_() { // these parameters guide the coupling between parameterizations // NOTE: mam4xx was ported with these parameters fixed, so it's probably not // NOTE: safe to change these without code modifications. + + // controls treatment of h2so4 condensation in mam_gasaerexch_1subarea + // 1 = sequential calc. of gas-chem prod then condensation loss + // 2 = simultaneous calc. of gas-chem prod and condensation loss config_.amicphys.gaexch_h2so4_uptake_optaa = 2; - config_.amicphys.newnuc_h2so4_conc_optaa = 2; + + // controls treatment of h2so4 concentrationin mam_newnuc_1subarea + // 1 = use average value calculated in standard cam5.2.10 and earlier + // 2 = use average value calculated in mam_gasaerexch_1subarea + // 11 = use average of initial and final values from mam_gasaerexch_1subarea + // 12 = use final value from mam_gasaerexch_1subarea + config_.amicphys.newnuc_h2so4_conc_optaa = 2; // LINOZ namelist parameters o3_lbl_ = m_params.get("mam4_o3_lbl"); - o3_tau_ = m_params.get("mam4_o3_tau"); - o3_sfc_ = m_params.get("mam4_o3_sfc"); + o3_tau_ = m_params.get("mam4_o3_tau"); + o3_sfc_ = m_params.get("mam4_o3_sfc"); +} + +AtmosphereProcessType MAMMicrophysics::type() const { + return AtmosphereProcessType::Physics; } // ================================================================ @@ -116,19 +90,19 @@ void MAMMicrophysics::set_grids( // ----------- Atmospheric quantities ------------- // Specific humidity [kg/kg](Require only for building DS) - add_field("qv", scalar3d_mid, q_unit, grid_name, "tracers"); + add_tracer("qv", grid_, kg/kg); // specific humidity // Cloud liquid mass mixing ratio [kg/kg](Require only for building DS) - add_field("qc", scalar3d_mid, q_unit, grid_name, "tracers"); + add_tracer("qc", grid_, kg/kg); // cloud liquid wet mixing ratio // Cloud ice mass mixing ratio [kg/kg](Require only for building DS) - add_field("qi", scalar3d_mid, q_unit, grid_name, "tracers"); + add_tracer("qi", grid_, kg/kg); // ice wet mixing ratio // Cloud liquid number mixing ratio [1/kg](Require only for building DS) - add_field("nc", scalar3d_mid, n_unit, grid_name, "tracers"); + add_tracer("nc", grid_, n_unit); // cloud liquid wet number mixing ratio // Cloud ice number mixing ratio [1/kg](Require only for building DS) - add_field("ni", scalar3d_mid, n_unit, grid_name, "tracers"); + add_tracer("ni", grid_, n_unit); // ice number mixing ratio // Temperature[K] at midpoints add_field("T_mid", scalar3d_mid, K, grid_name); @@ -190,15 +164,13 @@ void MAMMicrophysics::set_grids( for(int m = 0; m < nmodes; ++m) { const char *int_nmr_field_name = mam_coupling::int_aero_nmr_field_name(m); - add_field(int_nmr_field_name, scalar3d_mid, n_unit, grid_name, - "tracers"); + add_tracer(int_nmr_field_name, grid_, n_unit); for(int a = 0; a < mam_coupling::num_aero_species(); ++a) { const char *int_mmr_field_name = mam_coupling::int_aero_mmr_field_name(m, a); if(strlen(int_mmr_field_name) > 0) { - add_field(int_mmr_field_name, scalar3d_mid, q_unit, grid_name, - "tracers"); + add_tracer(int_mmr_field_name, grid_, kg/kg); } } // for loop species } // for loop nmodes interstitial @@ -220,21 +192,21 @@ void MAMMicrophysics::set_grids( // aerosol-related gases: mass mixing ratios for(int g = 0; g < mam_coupling::num_aero_gases(); ++g) { const char *gas_mmr_field_name = mam_coupling::gas_mmr_field_name(g); - add_field(gas_mmr_field_name, scalar3d_mid, q_unit, grid_name, - "tracers"); + add_tracer(gas_mmr_field_name, grid_, kg/kg); } // Creating a Linoz reader and setting Linoz parameters involves reading data // from a file and configuring the necessary parameters for the Linoz model. { linoz_file_name_ = m_params.get("mam4_linoz_file_name"); - std::string linoz_map_file = ""; - std::vector var_names{"o3_clim", "o3col_clim", "t_clim", - "PmL_clim", "dPmL_dO3", "dPmL_dT", - "dPmL_dO3col", "cariolle_pscs"}; + const std::string linoz_map_file = + m_params.get("aero_microphys_remap_file", ""); + const std::vector var_names{ + "o3_clim", "o3col_clim", "t_clim", "PmL_clim", + "dPmL_dO3", "dPmL_dT", "dPmL_dO3col", "cariolle_pscs"}; // in format YYYYMMDD - int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); + const int linoz_cyclical_ymd = m_params.get("mam4_linoz_ymd"); scream::mam_coupling::setup_tracer_data(linoz_data_, linoz_file_name_, linoz_cyclical_ymd); LinozHorizInterp_ = scream::mam_coupling::create_horiz_remapper( @@ -271,12 +243,13 @@ void MAMMicrophysics::set_grids( { oxid_file_name_ = m_params.get("mam4_oxid_file_name"); - std::string oxid_map_file = ""; + const std::string oxid_map_file = + m_params.get("aero_microphys_remap_file", ""); // NOTE: order matches mam4xx: - std::vector var_names{"O3", "OH", "NO3", "HO2"}; + const std::vector var_names{"O3", "OH", "NO3", "HO2"}; - // //in format YYYYMMDD - int oxid_ymd = m_params.get("mam4_oxid_ymd"); + // in format YYYYMMDD + const int oxid_ymd = m_params.get("mam4_oxid_ymd"); scream::mam_coupling::setup_tracer_data(tracer_data_, oxid_file_name_, oxid_ymd); TracerHorizInterp_ = scream::mam_coupling::create_horiz_remapper( @@ -306,16 +279,15 @@ void MAMMicrophysics::set_grids( } // oxid file reader { - // FIXME: I will need to add this file per forcing file. - std::string extfrc_map_file = ""; + const std::string extfrc_map_file = + m_params.get("aero_microphys_remap_file", ""); // NOTE: order of forcing species is important. // extfrc_lst(: 9) = {'SO2 ','so4_a1 ','so4_a2 // ','pom_a4 ','bc_a4 ', 'num_a1 ','num_a2 // ','num_a4 ','SOAG ' } // This order corresponds to files in namelist e3smv2 - extfrc_lst_ = - std::vector({"so2", "so4_a1", "so4_a2", "pom_a4", "bc_a4", - "num_a1", "num_a2", "num_a4", "soag"}); + extfrc_lst_ = {"so2", "so4_a1", "so4_a2", "pom_a4", "bc_a4", + "num_a1", "num_a2", "num_a4", "soag"}; for(const auto &var_name : extfrc_lst_) { std::string item_name = "mam4_" + var_name + "_verti_emiss_file_name"; @@ -357,11 +329,10 @@ void MAMMicrophysics::set_grids( } // var_name vert emissions int i = 0; int offset_emis_ver = 0; - for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { - const auto var_name = *it; + for(const auto &var_name : extfrc_lst_) { const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; - const int nvars = int(var_names.size()); + const int nvars = static_cast(var_names.size()); forcings_[i].nsectors = nvars; // I am assuming the order of species in extfrc_lst_. @@ -377,17 +348,18 @@ void MAMMicrophysics::set_grids( vert_emis_data_[i].allocate_temporal_views(); forcings_[i].file_alt_data = vert_emis_data_[i].has_altitude_; for(int isp = 0; isp < nvars; ++isp) { - EKAT_REQUIRE_MSG( - offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), - "Error! Number of fields is bigger than " - "MAX_NUM_VERT_EMISSION_FIELDS. Increase the " - "MAX_NUM_VERT_EMISSION_FIELDS in helper_micro.hpp \n"); forcings_[i].offset = offset_emis_ver; vert_emis_output_[isp + offset_emis_ver] = view_2d("vert_emis_output_", ncol_, nlev_); } offset_emis_ver += nvars; + ++i; } // end i + EKAT_REQUIRE_MSG( + offset_emis_ver <= int(mam_coupling::MAX_NUM_VERT_EMISSION_FIELDS), + "Error! Number of fields is bigger than " + "MAX_NUM_VERT_EMISSION_FIELDS. Increase the " + "MAX_NUM_VERT_EMISSION_FIELDS in tracer_reader_utils.hpp \n"); #if defined(ENABLE_OUTPUT_TRACER_FIELDS) FieldLayout scalar3d_mid_emis_ver = grid_->get_3d_vector_layout( @@ -418,15 +390,16 @@ size_t MAMMicrophysics::requested_buffer_size_in_bytes() const { // number of bytes allocated. void MAMMicrophysics::init_buffers(const ATMBufferManager &buffer_manager) { - EKAT_REQUIRE_MSG( - buffer_manager.allocated_bytes() >= requested_buffer_size_in_bytes(), - "Error! Insufficient buffer size.\n"); - size_t used_mem = mam_coupling::init_buffer(buffer_manager, ncol_, nlev_, buffer_); - EKAT_REQUIRE_MSG( - used_mem == requested_buffer_size_in_bytes(), - "Error! Used memory != requested memory for MAMMicrophysics."); + EKAT_REQUIRE_MSG(used_mem == requested_buffer_size_in_bytes(), + "Error! Used memory != requested memory for MAMMicrophysics." + " Used memory: " + << std::to_string(used_mem) + << "." + " Requested memory: " + << std::to_string(requested_buffer_size_in_bytes()) + << ". \n"); } // ================================================================ @@ -436,12 +409,12 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { // Determine orbital year. If orbital_year is negative, use current year // from timestamp for orbital year; if positive, use provided orbital year // for duration of simulation. - m_orbital_year = m_params.get("orbital_year", -9999); + m_orbital_year = m_params.get("orbital_year", -9999); // Get orbital parameters from yaml file - m_orbital_eccen = m_params.get("orbital_eccentricity", -9999); - m_orbital_obliq = m_params.get("orbital_obliquity", -9999); - m_orbital_mvelp = m_params.get("orbital_mvelp", -9999); + m_orbital_eccen = m_params.get("orbital_eccentricity", -9999); + m_orbital_obliq = m_params.get("orbital_obliquity", -9999); + m_orbital_mvelp = m_params.get("orbital_mvelp", -9999); // --------------------------------------------------------------- // Input fields read in from IC file, namelist or other processes @@ -582,8 +555,7 @@ void MAMMicrophysics::initialize_impl(const RunType run_type) { scream::mam_coupling::update_tracer_data_from_file( TracerDataReader_, curr_month, *TracerHorizInterp_, tracer_data_); - int i = 0; - for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { + for(int i = 0; i < static_cast(extfrc_lst_.size()); ++i) { scream::mam_coupling::update_tracer_data_from_file( VertEmissionsDataReader_[i], curr_month, *VertEmissionsHorizInterp_[i], vert_emis_data_[i]); @@ -705,8 +677,7 @@ void MAMMicrophysics::run_impl(const double dt) { vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); int i = 0; - for(auto it = extfrc_lst_.begin(); it != extfrc_lst_.end(); ++it, ++i) { - const auto var_name = *it; + for(const auto &var_name : extfrc_lst_) { const auto file_name = vert_emis_file_name_[var_name]; const auto var_names = vert_emis_var_names_[var_name]; const int nsectors = int(var_names.size()); @@ -718,6 +689,7 @@ void MAMMicrophysics::run_impl(const double dt) { VertEmissionsDataReader_[i], *VertEmissionsHorizInterp_[i], ts, vert_emiss_time_state_, vert_emis_data_[i], dry_atm_.p_mid, dry_atm_.z_iface, vert_emis_output); + i++; Kokkos::fence(); } @@ -737,10 +709,8 @@ void MAMMicrophysics::run_impl(const double dt) { mam_coupling::DryAtmosphere &dry_atm = dry_atm_; mam_coupling::AerosolState &dry_aero = dry_aero_; - mam_coupling::AerosolState &wet_aero = wet_aero_; mam4::mo_photo::PhotoTableData &photo_table = photo_table_; - const int nlev = nlev_; const Config &config = config_; const auto &work_photo_table = work_photo_table_; const auto &photo_rates = photo_rates_; @@ -753,12 +723,17 @@ void MAMMicrophysics::run_impl(const double dt) { // Note: We are following the RRTMGP EAMxx interface to compute the zenith // angle. This operation is performed on the host because the routine // shr_orb_cosz_c2f has not been ported to C++. - auto ts2 = timestamp(); - double obliqr, lambm0, mvelpp; + auto ts2 = timestamp(); auto orbital_year = m_orbital_year; - auto eccen = m_orbital_eccen; - auto obliq = m_orbital_obliq; - auto mvelp = m_orbital_mvelp; + // Note: We need double precision because + // shr_orb_params_c2f and shr_orb_decl_c2f only support double precision. + double obliqr, lambm0, mvelpp; + double eccen = m_orbital_eccen; + double obliq = m_orbital_obliq; + double mvelp = m_orbital_mvelp; + // Use the orbital parameters to calculate the solar declination and + // eccentricity factor + double delta, eccf; if(eccen >= 0 && obliq >= 0 && mvelp >= 0) { // use fixed orbital parameters; to force this, we need to set // orbital_year to SHR_ORB_UNDEF_INT, which is exposed through @@ -770,9 +745,7 @@ void MAMMicrophysics::run_impl(const double dt) { } shr_orb_params_c2f(&orbital_year, // in &eccen, &obliq, &mvelp, &obliqr, &lambm0, &mvelpp); // out - // Use the orbital parameters to calculate the solar declination and - // eccentricity factor - Real delta, eccf; + // Want day + fraction; calday 1 == Jan 1 0Z auto calday = ts2.frac_of_year_in_days() + 1; shr_orb_decl_c2f(calday, eccen, mvelpp, lambm0, obliqr, // in @@ -799,12 +772,8 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::deep_copy(acos_cosine_zenith_, acos_cosine_zenith_host_); } const auto zenith_angle = acos_cosine_zenith_; - - constexpr int num_modes = mam4::AeroConfig::num_modes(); constexpr int gas_pcnst = mam_coupling::gas_pcnst(); - constexpr int nqtendbb = mam_coupling::nqtendbb(); - constexpr int pcnst = mam4::pcnst; const auto vert_emis_output = vert_emis_output_; const auto extfrc = extfrc_; const auto forcings = forcings_; @@ -893,7 +862,8 @@ void MAMMicrophysics::run_impl(const double dt) { ekat::subview(linoz_dPmL_dO3col, icol); const auto linoz_cariolle_pscs_icol = ekat::subview(linoz_cariolle_pscs, icol); - // Note: All variables are inputs, except for progs, which is an input/output variable. + // Note: All variables are inputs, except for progs, which is an + // input/output variable. mam4::microphysics::perform_atmospheric_chemistry_and_microphysics( team, dt, rlats, cnst_offline_icol, forcings_in, atm, progs, photo_table, chlorine_loading, config.setsox, config.amicphys, diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index dbd97057123..248f0d62390 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -6,7 +6,7 @@ #include // For reading files -#include "impl/helper_micro.hpp" +#include "impl/tracer_reader_utils.hpp" // For calling MAM4 processes #include @@ -73,7 +73,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // The orbital year, used for zenith angle calculations: // If > 0, use constant orbital year for duration of simulation // If < 0, use year from timestamp for orbital parameters - Int m_orbital_year; + int m_orbital_year; // Orbital parameters, used for zenith angle calculations. // If >= 0, bypass computation based on orbital year and use fixed parameters @@ -222,9 +222,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // physics grid for column information std::shared_ptr grid_; - // sets defaults for "namelist parameters" - void set_namelist_params_(); - mam_coupling::TracerTimeState linoz_time_state_; view_2d work_photo_table_; std::vector chlorine_values_; diff --git a/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp index f0374a5617d..863da8021dd 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_wetscav_process_interface.cpp @@ -28,8 +28,8 @@ void MAMWetscav::set_grids( // The units of mixing ratio Q are technically non-dimensional. // Nevertheless, for output reasons, we like to see 'kg/kg'. - auto q_unit = kg / kg; - auto n_unit = 1 / kg; // units of number mixing ratios of tracers + auto q_unit = kg / kg; + auto n_unit = 1 / kg; // units of number mixing ratios of tracers m_grid = grids_manager->get_grid("Physics"); const auto &grid_name = m_grid->name(); diff --git a/components/eamxx/src/physics/mam/impl/README.md b/components/eamxx/src/physics/mam/impl/README.md deleted file mode 100644 index 1983ed0c53d..00000000000 --- a/components/eamxx/src/physics/mam/impl/README.md +++ /dev/null @@ -1,11 +0,0 @@ -#MAM4 Integration Code - -This folder contains C++ implementations of the higher-level MAM4 interface -routines for aerosol microphysics, cloud-aerosol interactions, and optical -properties. We've retained the overall structure of the original Fortran code -to make it easier to understand for folks who are more familiar with the -original implementation of MAM4. - -## Contents - -* `mam4_amicphys.cpp` - high-level MAM4 microphysics interface code diff --git a/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp b/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp index 67d8ea761ef..ab5a0509274 100644 --- a/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp +++ b/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp @@ -41,7 +41,7 @@ mam4::mo_photo::PhotoTableData read_photo_table( const std::string &rsf_file, const std::string &xs_long_file) { // set up the lng_indexer and pht_alias_mult_1 views based on our // (hardwired) chemical mechanism - HostViewInt1D lng_indexer_h("lng_indexer(host)", phtcnt); + HostViewInt1D lng_indexer_h("lng_indexer", phtcnt); int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions scorpio::register_file(rsf_file, scorpio::Read); diff --git a/components/eamxx/src/physics/mam/impl/helper_micro.hpp b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp similarity index 94% rename from components/eamxx/src/physics/mam/impl/helper_micro.hpp rename to components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp index c8e1c0cc831..9c4ec1065fa 100644 --- a/components/eamxx/src/physics/mam/impl/helper_micro.hpp +++ b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp @@ -1,5 +1,5 @@ -#ifndef EAMXX_MAM_HELPER_MICRO -#define EAMXX_MAM_HELPER_MICRO +#ifndef EAMXX_MAM_TRACER_READER_UTILS +#define EAMXX_MAM_TRACER_READER_UTILS #include #include @@ -35,20 +35,16 @@ inline void compute_p_src_zonal_files(const view_1d &levs, const int ncol = p_src.extent(0); const int nlevs_data = levs.extent(0); EKAT_REQUIRE_MSG( - int(p_src.extent(1)) == nlevs_data, + p_src.extent_int(1) == nlevs_data, "Error: p_src has a different number of levels than the source data. \n"); - - const auto policy_pressure = ESU::get_default_team_policy(ncol, nlevs_data); Kokkos::parallel_for( - "pressure_computation", policy_pressure, KOKKOS_LAMBDA(const Team &team) { - const int icol = team.league_rank(); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevs_data), - [&](const Int &kk) { - // mbar->pascals - // FIXME: Does EAMxx have a better method to - // convert units?" - p_src(icol, kk) = levs(kk) * 100; - }); + "pressure_computation", + Kokkos::MDRangePolicy >({0, 0}, {ncol, nlevs_data}), + KOKKOS_LAMBDA(const int icol, const int kk) { + // mbar->pascals + // FIXME: Does EAMxx have a better method to + // convert units?" + p_src(icol, kk) = levs(kk) * 100; }); Kokkos::fence(); } @@ -202,9 +198,12 @@ inline util::TimeStamp convert_date(const int date) { int day = date - year * ten_thousand - month * one_hundred; return util::TimeStamp(year, month, day, 0, 0, 0); } -// FIXME: check if this function is implemented in eamxx -// Assumes 365 days/year, 30 days/month -inline int compute_days(const util::TimeStamp &ts) { +// FIXME: This function is not implemented in eamxx. +// FIXME: Assumes 365 days/year, 30 days/month; +// NOTE: that this assumption is mainly used for plotting. +// NOTE: This is not a direct port from EAM. +//We only use this routine for chlorine. +inline int compute_number_days_from_zero(const util::TimeStamp &ts) { return ts.get_year() * 365 + ts.get_month() * 30 + ts.get_day(); } @@ -215,7 +214,7 @@ inline void create_linoz_chlorine_reader( auto time_stamp_beg = convert_date(chlorine_loading_ymd); const int offset_time = - compute_days(time_stamp_beg) - compute_days(model_time); + compute_number_days_from_zero(time_stamp_beg) - compute_number_days_from_zero(model_time); scorpio::register_file(linoz_chlorine_file, scorpio::Read); const int nlevs_time = scorpio::get_time_len(linoz_chlorine_file); for(int itime = 0; itime < nlevs_time; ++itime) { @@ -226,7 +225,7 @@ inline void create_linoz_chlorine_reader( scorpio::read_var(linoz_chlorine_file, "chlorine_loading", &value, itime); values.push_back(value); auto time_stamp = convert_date(date); - time_secs.push_back(compute_days(time_stamp) - offset_time); + time_secs.push_back(compute_number_days_from_zero(time_stamp) - offset_time); } } // end itime scorpio::release_file(linoz_chlorine_file); @@ -267,7 +266,7 @@ inline void get_time_from_ncfile(const std::string &file_name, inline Real chlorine_loading_advance(const util::TimeStamp &ts, std::vector &values, std::vector &time_secs) { - const int current_time = compute_days(ts); + const int current_time = compute_number_days_from_zero(ts); int index = 0; // update index for(int i = 0; i < int(values.size()); i++) { @@ -612,7 +611,7 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, const TracerData &input, const view_2d output[]) { // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_ == int(output[0].extent(0))); + EKAT_REQUIRE(input.ncol_ == output[0].extent_int(0)); const int ncol = input.ncol_; const int levsiz = input.nlev_; const int pver = mam4::nlev; @@ -652,11 +651,11 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, EKAT_REQUIRE_MSG( input.file_type == VERT_EMISSION, "Error! vertical interpolation only with altitude variable. \n"); - const int ncols = input.ncol_; - const int num_vars = input.nvars_; - const int ntrg = output[0].extent(1); - const int num_vert_packs = ntrg; - const int outer_iters = ncols * num_vars; + const int ncols = input.ncol_; + const int num_vars = input.nvars_; + const int num_vertical_lev_target = output[0].extent(1); + const int num_vert_packs = num_vertical_lev_target; + const int outer_iters = ncols * num_vars; const auto policy_interp = ESU::get_default_team_policy(outer_iters, num_vert_packs); // FIXME: Get m2km from emaxx. @@ -684,8 +683,8 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, trg_x[pverp - i - 1] = m2km * zi(icol, i); } team.team_barrier(); - mam4::vertical_interpolation::rebin(team, nsrc, ntrg, src_x, trg_x, src, - trg); + mam4::vertical_interpolation::rebin(team, nsrc, num_vertical_lev_target, + src_x, trg_x, src, trg); }); } diff --git a/components/eamxx/src/share/io/scream_scorpio_interface.cpp b/components/eamxx/src/share/io/scream_scorpio_interface.cpp index 2075ef824ff..8d2f64994dd 100644 --- a/components/eamxx/src/share/io/scream_scorpio_interface.cpp +++ b/components/eamxx/src/share/io/scream_scorpio_interface.cpp @@ -360,11 +360,11 @@ void finalize_subsystem () EKAT_REQUIRE_MSG (s.pio_sysid!=-1, "Error! PIO subsystem was already finalized.\n"); - /*for (auto& it : s.files) { + for (auto& it : s.files) { EKAT_REQUIRE_MSG (it.second.num_customers==0, "Error! ScorpioSession::finalize called, but a file is still in use elsewhere.\n" " - filename: " + it.first + "\n"); - }*/ + } s.files.clear(); for (auto& it : s.decomps) { diff --git a/components/eamxx/tests/multi-process/dynamics_physics/mam/homme_shoc_cld_spa_p3_rrtmgp_mam4_wetscav/output.yaml b/components/eamxx/tests/multi-process/dynamics_physics/mam/homme_shoc_cld_spa_p3_rrtmgp_mam4_wetscav/output.yaml index 28c3e31ff19..a48a4eba3c8 100644 --- a/components/eamxx/tests/multi-process/dynamics_physics/mam/homme_shoc_cld_spa_p3_rrtmgp_mam4_wetscav/output.yaml +++ b/components/eamxx/tests/multi-process/dynamics_physics/mam/homme_shoc_cld_spa_p3_rrtmgp_mam4_wetscav/output.yaml @@ -103,7 +103,7 @@ Fields: - aerdepwetis - aerdepwetcw - dgnumwet - - dgncur_a + - dgnum - wetdens - qaerwat - bc_c1 diff --git a/components/eamxx/tests/multi-process/physics_only/mam/p3_mam4_wetscav/output.yaml b/components/eamxx/tests/multi-process/physics_only/mam/p3_mam4_wetscav/output.yaml index c26db8eb681..7d4d355ecc6 100644 --- a/components/eamxx/tests/multi-process/physics_only/mam/p3_mam4_wetscav/output.yaml +++ b/components/eamxx/tests/multi-process/physics_only/mam/p3_mam4_wetscav/output.yaml @@ -57,7 +57,7 @@ Field Names: - aerdepwetis - aerdepwetcw - dgnumwet - - dgncur_a + - dgnum - wetdens - qaerwat output_control: diff --git a/components/eamxx/tests/multi-process/physics_only/mam/shoc_cldfrac_p3_wetscav/output.yaml b/components/eamxx/tests/multi-process/physics_only/mam/shoc_cldfrac_p3_wetscav/output.yaml index 1ae53e6c1c3..da4a83ad7b1 100644 --- a/components/eamxx/tests/multi-process/physics_only/mam/shoc_cldfrac_p3_wetscav/output.yaml +++ b/components/eamxx/tests/multi-process/physics_only/mam/shoc_cldfrac_p3_wetscav/output.yaml @@ -91,7 +91,7 @@ Field Names: - aerdepwetis - aerdepwetcw - dgnumwet - - dgncur_a + - dgnum - wetdens - qaerwat output_control: diff --git a/components/eamxx/tests/single-process/mam/wet_scav/output.yaml b/components/eamxx/tests/single-process/mam/wet_scav/output.yaml index 9cd0a16bc41..854d18a2e48 100644 --- a/components/eamxx/tests/single-process/mam/wet_scav/output.yaml +++ b/components/eamxx/tests/single-process/mam/wet_scav/output.yaml @@ -8,7 +8,7 @@ Fields: - aerdepwetis - aerdepwetcw - dgnumwet - - dgncur_a + - dgnum - wetdens - qaerwat - bc_c1 diff --git a/externals/mam4xx b/externals/mam4xx index d2a862dfc90..ed3a8d79494 160000 --- a/externals/mam4xx +++ b/externals/mam4xx @@ -1 +1 @@ -Subproject commit d2a862dfc909ca782afd86ab14fdbc248403aba4 +Subproject commit ed3a8d7949462f3e80524f76f85e2858284c2be6 From 4027f68f084dfa17c06683db4d2ae49dda08b53d Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Fri, 25 Oct 2024 13:18:22 -0600 Subject: [PATCH 24/31] Correcting data types for variables, refining comments, and updating YAML input configurations. --- ...mxx_mam_microphysics_process_interface.hpp | 7 +++--- .../physics/mam/impl/tracer_reader_utils.hpp | 2 +- .../mam/aero_microphys/input.yaml | 24 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 248f0d62390..6cffdd98171 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -78,9 +78,10 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // Orbital parameters, used for zenith angle calculations. // If >= 0, bypass computation based on orbital year and use fixed parameters // If < 0, compute based on orbital year, specified above - Real m_orbital_eccen; // Eccentricity - Real m_orbital_obliq; // Obliquity - Real m_orbital_mvelp; // Vernal Equinox Mean Longitude of Perihelion + // These variables are required to be double. + double m_orbital_eccen; // Eccentricity + double m_orbital_obliq; // Obliquity + double m_orbital_mvelp; // Vernal Equinox Mean Longitude of Perihelion // configuration data (for the moment, we plan to be able to move this to // the device, so we can't use C++ strings) diff --git a/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp index 9c4ec1065fa..7fb8858e0cc 100644 --- a/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp +++ b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp @@ -521,7 +521,7 @@ inline void update_tracer_timestate( tracer_horiz_interp, data_tracer); } -} // END updata_spa_timestate +} // END update_tracer_timestate // This function is based on the SPA::perform_time_interpolation function. inline void perform_time_interpolation(const TracerTimeState &time_state, diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index 44bb9ee687e..b74718d67a0 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -11,29 +11,27 @@ time_stepping: atmosphere_processes: atm_procs_list: [mam4_aero_microphys] mam4_aero_microphys: - mam4_do_cond: true - mam4_do_newnuc: true - mam4_do_coag: true - mam4_do_rename: true - mam4_o3_tau: 172800.0 - mam4_o3_sfc: 3.0E-008 - mam4_o3_lbl: 4 - - mam4_aero_microphys: - mam4_linoz_ymd : 20100101 + mam4_do_cond: true + mam4_do_newnuc: true + mam4_do_coag: true + mam4_do_rename: true + mam4_o3_tau: 172800.0 + mam4_o3_sfc: 3.0E-008 + mam4_o3_lbl: 4 + mam4_linoz_ymd : 20100101 mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_ne2np4_L26_1850-2015_c20240827.nc mam4_oxid_ymd : 20150101 mam4_linoz_chlorine_file : ${SCREAM_DATA_DIR}/mam4xx/linoz/Linoz_Chlorine_Loading_CMIP6_0003-2017_c20171114.nc - mam4_chlorine_loading_ymd : 20100101 + mam4_chlorine_loading_ymd : 20100101 mam4_rsf_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc mam4_xs_long_file : ${SCREAM_DATA_DIR}/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc verti_emiss_ymd : 20100101 - mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726.nc + mam4_so2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_so2_elev_ne2np4_2010_clim_c20240726.nc mam4_so4_a1_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a1_elev_ne2np4_2010_clim_c20240823.nc mam4_so4_a2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_so4_a2_elev_ne2np4_2010_clim_c20240823.nc mam4_pom_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_pom_a4_elev_ne2np4_2010_clim_c20240823.nc - mam4_bc_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240823.nc + mam4_bc_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated/cmip6_mam4_bc_a4_elev_ne2np4_2010_clim_c20240823.nc mam4_num_a1_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a1_elev_ne2np4_2010_clim_c20240823.nc mam4_num_a2_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a2_elev_ne2np4_2010_clim_c20240823.nc mam4_num_a4_verti_emiss_file_name : ${SCREAM_DATA_DIR}/mam4xx/emissions/ne2np4/elevated//cmip6_mam4_num_a4_elev_ne2np4_2010_clim_c20240823.nc From 0227bdd3e8f62f2b68a71fbaf3438736786db428 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Mon, 28 Oct 2024 14:57:38 -0600 Subject: [PATCH 25/31] EAMxx: Refine linoz parameter handling and class member references - Implement linoz struct usage, replacing individual class members for clarity and efficiency. - Correct missing references (&) in class members to prevent runtime errors in pm-gpu configurations. - Remove unused parameters and structs to clean up the codebase. --- .../cime_config/namelist_defaults_scream.xml | 1 + ...mxx_mam_microphysics_process_interface.cpp | 21 ++++----- ...mxx_mam_microphysics_process_interface.hpp | 24 ++-------- .../physics/mam/impl/tracer_reader_utils.hpp | 45 ++++++++++++------- .../mam/aero_microphys/input.yaml | 1 + 5 files changed, 44 insertions(+), 48 deletions(-) diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 70d771e0440..221eaf1e585 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -280,6 +280,7 @@ be lost if SCREAM_HACK_XML is not enabled. 172800.0 3.0E-008 4 + 193.0 20100101 ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne30pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne30pg2_c20240724.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/linoz/ne4pg2/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne4pg2_c20240724.nc diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index a51fa1c1ce3..02d8b020243 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -43,9 +43,10 @@ MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, config_.amicphys.newnuc_h2so4_conc_optaa = 2; // LINOZ namelist parameters - o3_lbl_ = m_params.get("mam4_o3_lbl"); - o3_tau_ = m_params.get("mam4_o3_tau"); - o3_sfc_ = m_params.get("mam4_o3_sfc"); + config_.linoz.o3_lbl = m_params.get("mam4_o3_lbl"); + config_.linoz.o3_tau = m_params.get("mam4_o3_tau"); + config_.linoz.o3_sfc = m_params.get("mam4_o3_sfc"); + config_.linoz.psc_T = m_params.get("mam4_psc_T"); } AtmosphereProcessType MAMMicrophysics::type() const { @@ -774,9 +775,9 @@ void MAMMicrophysics::run_impl(const double dt) { const auto zenith_angle = acos_cosine_zenith_; constexpr int gas_pcnst = mam_coupling::gas_pcnst(); - const auto vert_emis_output = vert_emis_output_; - const auto extfrc = extfrc_; - const auto forcings = forcings_; + const auto& vert_emis_output = vert_emis_output_; + const auto& extfrc = extfrc_; + const auto& forcings = forcings_; constexpr int extcnt = mam4::gas_chemistry::extcnt; const int offset_aerosol = mam4::utils::gasses_start_ind(); @@ -792,11 +793,6 @@ void MAMMicrophysics::run_impl(const double dt) { clsmap_4[i] = mam4::gas_chemistry::clsmap_4[i]; permute_4[i] = mam4::gas_chemistry::permute_4[i]; } - // LINOZ parameters from the namelist - const int o3_lbl = o3_lbl_; - const Real o3_tau = o3_tau_; - const Real o3_sfc = o3_sfc_; - // loop over atmosphere columns and compute aerosol microphyscs Kokkos::parallel_for( policy, KOKKOS_LAMBDA(const ThreadTeam &team) { @@ -873,7 +869,8 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_o3col_clim_icol, linoz_PmL_clim_icol, linoz_dPmL_dO3_icol, linoz_dPmL_dT_icol, linoz_dPmL_dO3col_icol, linoz_cariolle_pscs_icol, eccf, adv_mass_kg_per_moles, clsmap_4, - permute_4, offset_aerosol, o3_sfc, o3_tau, o3_lbl, + permute_4, offset_aerosol, + config.linoz.o3_sfc, config.linoz.o3_tau, config.linoz.o3_lbl, dry_diameter_icol, wet_diameter_icol, wetdens_icol); }); // parallel_for for the column loop Kokkos::fence(); diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 6cffdd98171..1c905917d51 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -66,10 +66,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // number of horizontal columns and vertical levels int ncol_, nlev_; - // Namelist for LINOZ - int o3_lbl_; - Real o3_tau_, o3_sfc_; - // The orbital year, used for zenith angle calculations: // If > 0, use constant orbital year for duration of simulation // If < 0, use year from timestamp for orbital parameters @@ -83,23 +79,14 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { double m_orbital_obliq; // Obliquity double m_orbital_mvelp; // Vernal Equinox Mean Longitude of Perihelion - // configuration data (for the moment, we plan to be able to move this to - // the device, so we can't use C++ strings) -#define MAX_FILENAME_LEN 256 struct Config { - // photolysis parameters - struct { - char rsf_file[MAX_FILENAME_LEN]; - char xs_long_file[MAX_FILENAME_LEN]; - } photolysis; // stratospheric chemistry parameters struct { int o3_lbl; // number of layers with ozone decay from the surface - int o3_sfc; // set from namelist input linoz_sfc - int o3_tau; // set from namelist input linoz_tau + Real o3_sfc; // set from namelist input linoz_sfc + Real o3_tau; // set from namelist input linoz_tau Real psc_T; // set from namelist input linoz_psc_T - char chlorine_loading_file[MAX_FILENAME_LEN]; } linoz; // aqueous chemistry parameters @@ -107,11 +94,6 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { // aero microphysics configuration (see impl/mam4_amicphys.cpp) mam4::microphysics::AmicPhysConfig amicphys; - - // dry deposition parameters - struct { - char srf_file[MAX_FILENAME_LEN]; - } drydep; }; Config config_; @@ -211,7 +193,7 @@ class MAMMicrophysics final : public scream::AtmosphereProcess { mam4::mo_photo::PhotoTableData photo_table_; // column areas, latitudes, longitudes - const_view_1d col_latitudes_, col_longitudes_; + const_view_1d col_latitudes_; // surface albedo: shortwave, direct const_view_1d d_sfc_alb_dir_vis_; diff --git a/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp index 7fb8858e0cc..2e34db2b496 100644 --- a/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp +++ b/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp @@ -134,7 +134,6 @@ struct TracerData { const_view_1d hyam; const_view_1d hybm; view_int_1d work_vert_inter[MAX_NVARS_TRACER]; - ; // External forcing file (vertical emission) // Uses altitude instead of pressure to interpolate data @@ -534,10 +533,10 @@ inline void perform_time_interpolation(const TracerTimeState &time_state, auto &delta_t = time_state.days_this_month; // We can ||ize over columns as well as over variables and bands - const auto data = data_tracer.data; - const auto file_type = data_tracer.file_type; + const auto& data = data_tracer.data; + const auto& file_type = data_tracer.file_type; - const auto ps = data_tracer.ps; + const auto& ps = data_tracer.ps; const int num_vars = data_tracer.nvars_; const int ncol = data_tracer.ncol_; @@ -610,20 +609,25 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, const const_view_2d &p_tgt_c, const TracerData &input, const view_2d output[]) { - // At this stage, begin/end must have the same horiz dimensions - EKAT_REQUIRE(input.ncol_ == output[0].extent_int(0)); const int ncol = input.ncol_; const int levsiz = input.nlev_; const int pver = mam4::nlev; const int num_vars = input.nvars_; - // FIXME:delete work[ivar] - const auto work = input.work_vert_inter; - EKAT_REQUIRE(work[num_vars - 1].data() != 0); - // vert_interp is serial in col and lev. + // make a local copy of output + view_2d output_local[MAX_NVARS_TRACER]; + EKAT_REQUIRE_MSG( + num_vars <= int(MAX_NVARS_TRACER), + "Error! Number of variables is bigger than NVARS_MAXTRACER. \n"); + for (int ivar = 0; ivar < num_vars; ++ivar) + { + // At this stage, begin/end must have the same horiz dimensions + EKAT_REQUIRE(input.ncol_ == output[ivar].extent_int(0)); + output_local[ivar] = output[ivar]; + } const int outer_iters = ncol * num_vars; const auto policy_setup = ESU::get_default_team_policy(outer_iters, pver); - const auto data = input.data; + const auto& data = input.data; Kokkos::parallel_for( "vert_interp", policy_setup, @@ -633,9 +637,9 @@ inline void perform_vertical_interpolation(const view_2d &p_src_c, const int ivar = team.league_rank() % num_vars; const auto pin_at_icol = ekat::subview(p_src_c, icol); const auto pmid_at_icol = ekat::subview(p_tgt_c, icol); - const auto datain = data[TracerDataIndex::OUT][ivar]; + const auto& datain = data[TracerDataIndex::OUT][ivar]; const auto datain_at_icol = ekat::subview(datain, icol); - const auto dataout = output[ivar]; + const auto dataout = output_local[ivar]; const auto dataout_at_icol = ekat::subview(dataout, icol); mam4::vertical_interpolation::vert_interp( @@ -664,8 +668,19 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const int nsrc = input.nlev_; constexpr int pver = mam4::nlev; const int pverp = pver + 1; - const auto data = input.data; + const auto& data = input.data; + // make a local copy of output + view_2d output_local[MAX_NVARS_TRACER]; + EKAT_REQUIRE_MSG( + num_vars <= int(MAX_NVARS_TRACER), + "Error! Number of variables is bigger than NVARS_MAXTRACER. \n"); + for (int ivar = 0; ivar < num_vars; ++ivar) + { + // At this stage, begin/end must have the same horiz dimensions + EKAT_REQUIRE(input.ncol_ == output[ivar].extent_int(0)); + output_local[ivar] = output[ivar]; + } Kokkos::parallel_for( "tracer_vert_interp_loop", policy_interp, KOKKOS_LAMBDA(const Team &team) { @@ -673,7 +688,7 @@ inline void perform_vertical_interpolation(const const_view_1d &altitude_int, const int ivar = team.league_rank() % num_vars; const auto src = ekat::subview(data[TracerDataIndex::OUT][ivar], icol); - const auto trg = ekat::subview(output[ivar], icol); + const auto trg = ekat::subview(output_local[ivar], icol); // FIXME: Try to avoid copy of trg_x by modifying rebin // trg_x Real trg_x[pver + 1]; diff --git a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml index b74718d67a0..fa4538bb75a 100644 --- a/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml +++ b/components/eamxx/tests/single-process/mam/aero_microphys/input.yaml @@ -18,6 +18,7 @@ atmosphere_processes: mam4_o3_tau: 172800.0 mam4_o3_sfc: 3.0E-008 mam4_o3_lbl: 4 + mam4_psc_T : 193.0 mam4_linoz_ymd : 20100101 mam4_linoz_file_name : ${SCREAM_DATA_DIR}/mam4xx/linoz/ne2np4/linoz1850-2015_2010JPL_CMIP6_10deg_58km_ne2np4_c20240724.nc mam4_oxid_file_name : ${SCREAM_DATA_DIR}/mam4xx/invariants/ne2np4/oxid_ne2np4_L26_1850-2015_c20240827.nc From 6f6342116d0ec473012263201573db8d2cbc8204 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 30 Oct 2024 20:34:14 -0600 Subject: [PATCH 26/31] EAMxx: Move read utils files to readfiles folder. --- .../physics/mam/eamxx_mam_microphysics_process_interface.cpp | 3 ++- .../physics/mam/eamxx_mam_microphysics_process_interface.hpp | 2 +- .../src/physics/mam/{impl => readfiles}/photo_table_utils.cpp | 0 .../physics/mam/{impl => readfiles}/tracer_reader_utils.hpp | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename components/eamxx/src/physics/mam/{impl => readfiles}/photo_table_utils.cpp (100%) rename components/eamxx/src/physics/mam/{impl => readfiles}/tracer_reader_utils.hpp (100%) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 02d8b020243..1e8892c4ab9 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -1,7 +1,8 @@ #include // impl namespace for some driver level functions for microphysics -#include "impl/photo_table_utils.cpp" +#include "readfiles/tracer_reader_utils.hpp" +#include "readfiles/photo_table_utils.cpp" #include "physics/rrtmgp/shr_orb_mod_c2f.hpp" // When the preprocessor definition ENABLE_OUTPUT_TRACER_FIELDS is diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 1c905917d51..32cfbc13ca1 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -6,7 +6,7 @@ #include // For reading files -#include "impl/tracer_reader_utils.hpp" +#include "readfiles/tracer_reader_utils.hpp" // For calling MAM4 processes #include diff --git a/components/eamxx/src/physics/mam/impl/photo_table_utils.cpp b/components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp similarity index 100% rename from components/eamxx/src/physics/mam/impl/photo_table_utils.cpp rename to components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp diff --git a/components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp b/components/eamxx/src/physics/mam/readfiles/tracer_reader_utils.hpp similarity index 100% rename from components/eamxx/src/physics/mam/impl/tracer_reader_utils.hpp rename to components/eamxx/src/physics/mam/readfiles/tracer_reader_utils.hpp From ff02fcbbf4b988458fca82702a10b99acb238c09 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 30 Oct 2024 20:36:12 -0600 Subject: [PATCH 27/31] EAMxx: Use a temporary branch for mam4xx to test changes in the photo table code. --- externals/mam4xx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/mam4xx b/externals/mam4xx index ed3a8d79494..f7d07c5a359 160000 --- a/externals/mam4xx +++ b/externals/mam4xx @@ -1 +1 @@ -Subproject commit ed3a8d7949462f3e80524f76f85e2858284c2be6 +Subproject commit f7d07c5a3592a5e89101135681205e5e459f020f From 4943440bfa3bc289a24f9573b5a423fbffa1692c Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 30 Oct 2024 20:42:33 -0600 Subject: [PATCH 28/31] EAMxx: Update description of dry aerosol particle diameters. --- .../eamxx/cime_config/namelist_defaults_scream.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/eamxx/cime_config/namelist_defaults_scream.xml b/components/eamxx/cime_config/namelist_defaults_scream.xml index 221eaf1e585..a1435b03de1 100644 --- a/components/eamxx/cime_config/namelist_defaults_scream.xml +++ b/components/eamxx/cime_config/namelist_defaults_scream.xml @@ -348,7 +348,7 @@ be lost if SCREAM_HACK_XML is not enabled. - + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/DMSflux.2010.ne30pg2_conserv.POPmonthlyClimFromACES4BGC_c20240816.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so2_surf_ne30pg2_2010_clim_c20240816.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_bc_a4_surf_ne30pg2_2010_clim_c20240816.nc @@ -358,8 +358,8 @@ be lost if SCREAM_HACK_XML is not enabled. ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_pom_a4_surf_ne30pg2_2010_clim_c20240816.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a1_surf_ne30pg2_2010_clim_c20240816.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a2_surf_ne30pg2_2010_clim_c20240816.nc - - + + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/DMSflux.2010.ne4pg2_conserv.POPmonthlyClimFromACES4BGC_c20240814.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so2_surf_ne4pg2_2010_clim_c20240815.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_bc_a4_surf_ne4pg2_2010_clim_c20240815.nc @@ -369,7 +369,7 @@ be lost if SCREAM_HACK_XML is not enabled. ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_pom_a4_surf_ne4pg2_2010_clim_c20240815.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a1_surf_ne4pg2_2010_clim_c20240815.nc ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a2_surf_ne4pg2_2010_clim_c20240815.nc - + ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/DMSflux.2010.ne4pg2_conserv.POPmonthlyClimFromACES4BGC_c20240814.nc @@ -634,7 +634,7 @@ be lost if SCREAM_HACK_XML is not enabled. 0.0 0.0,0.0 - 1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08 + 1.37146e-07 ,3.45899e-08 ,1.00000e-06 ,9.99601e-08 1.37452e-07 ,3.46684e-08 ,1.00900e-06 ,9.99601e-08 5.08262e-12 ,1.54035e-13 ,3.09018e-13 ,9.14710e-22 0.0 @@ -704,7 +704,7 @@ be lost if SCREAM_HACK_XML is not enabled. + --> From 3943f805a564f3876e29b302a35b69a60a8679cf Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 30 Oct 2024 21:11:05 -0600 Subject: [PATCH 29/31] EAMxx: Remove temporary CPP macro. --- ...mxx_mam_microphysics_process_interface.cpp | 72 ------------------- ...mxx_mam_microphysics_process_interface.hpp | 3 - 2 files changed, 75 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 1e8892c4ab9..6ef28614315 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -5,18 +5,6 @@ #include "readfiles/photo_table_utils.cpp" #include "physics/rrtmgp/shr_orb_mod_c2f.hpp" -// When the preprocessor definition ENABLE_OUTPUT_TRACER_FIELDS is -// enabled, the fields oxi_fields, linoz_fields, and -// vertical_emission_fields will be saved. -// These fields, which are the output of the tracer reader, -// have been stored in the FM for evaluation purposes. -// There are 33 fields (ncolxnlev) in total. -// Therefore, it is recommended to disable -// the ENABLE_OUTPUT_TRACER_FIELDS preprocessor -// definition once the evaluation of the microphysics interface -// is completed. -#define ENABLE_OUTPUT_TRACER_FIELDS - namespace scream { MAMMicrophysics::MAMMicrophysics(const ekat::Comm &comm, @@ -226,21 +214,6 @@ void MAMMicrophysics::set_grids( const int nvars = int(var_names.size()); linoz_data_.init(num_cols_io_linoz, num_levs_io_linoz, nvars); linoz_data_.allocate_temporal_views(); - -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - FieldLayout scalar3d_mid_linoz = - grid_->get_3d_vector_layout(true, nvars, "nlinoz_fields"); - // Note: Using nondim because the linoz fields have difference type of units - /* linoz_o3_clim : ozone (climatology) [vmr] - linoz_t_clim ! temperature (climatology) [K] - linoz_o3col_clim Column O3 above box (climatology) [Dobson Units or DU] - linoz_PmL_clim P minus L (climatology) [vmr/s] - linoz_dPmL_dO3 Sensitivity of P minus L to O3 [1/s] - linoz_dPmL_dT Sensitivity of P minus L to T [K] - linoz_dPmL_dO3col Sensitivity of P minus L to overhead O3 column [vmr/DU] - linoz_cariolle_psc Cariolle parameter for PSC loss of ozone [1/s] */ - add_field("linoz_fields", scalar3d_mid_linoz, nondim, grid_name); -#endif } // LINOZ reader { @@ -271,13 +244,6 @@ void MAMMicrophysics::set_grids( for(int ivar = 0; ivar < nvars; ++ivar) { cnst_offline_[ivar] = view_2d("cnst_offline_", ncol_, nlev_); } - -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - FieldLayout scalar3d_mid_oxi = - grid_->get_3d_vector_layout(true, nvars, "noxid_fields"); - // NOTE: Assuming nondim for units. - add_field("oxi_fields", scalar3d_mid_oxi, nondim, grid_name); -#endif } // oxid file reader { @@ -363,13 +329,6 @@ void MAMMicrophysics::set_grids( "MAX_NUM_VERT_EMISSION_FIELDS. Increase the " "MAX_NUM_VERT_EMISSION_FIELDS in tracer_reader_utils.hpp \n"); -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - FieldLayout scalar3d_mid_emis_ver = grid_->get_3d_vector_layout( - true, offset_emis_ver, "nvertical_emission"); - // NOTE: Assuming nondim for units. - add_field("vertical_emission_fields", scalar3d_mid_emis_ver, - nondim, grid_name); -#endif } // Tracer external forcing data } // set_grids @@ -648,16 +607,6 @@ void MAMMicrophysics::run_impl(const double dt) { cnst_offline_); // out Kokkos::fence(); -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - const auto oxi_fields_outputs = - get_field_out("oxi_fields").get_view(); - for(int ifield = 0; ifield < int(oxi_fields_outputs.extent(1)); ++ifield) { - const auto field_at_i = Kokkos::subview(oxi_fields_outputs, Kokkos::ALL(), - ifield, Kokkos::ALL()); - Kokkos::deep_copy(field_at_i, cnst_offline_[ifield]); - } -#endif - scream::mam_coupling::advance_tracer_data( LinozDataReader_, // in *LinozHorizInterp_, // out @@ -667,16 +616,6 @@ void MAMMicrophysics::run_impl(const double dt) { linoz_output); // out Kokkos::fence(); -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - const auto linoz_fields_outputs = - get_field_out("linoz_fields").get_view(); - for(int ifield = 0; ifield < int(linoz_fields_outputs.extent(1)); ++ifield) { - const auto field_at_i = Kokkos::subview(linoz_fields_outputs, Kokkos::ALL(), - ifield, Kokkos::ALL()); - Kokkos::deep_copy(field_at_i, linoz_output[ifield]); - } -#endif - vert_emiss_time_state_.t_now = ts.frac_of_year_in_days(); int i = 0; for(const auto &var_name : extfrc_lst_) { @@ -695,17 +634,6 @@ void MAMMicrophysics::run_impl(const double dt) { Kokkos::fence(); } -#if defined(ENABLE_OUTPUT_TRACER_FIELDS) - const auto ver_emiss_fields_outputs = - get_field_out("vertical_emission_fields").get_view(); - for(int ifield = 0; ifield < int(ver_emiss_fields_outputs.extent(1)); - ++ifield) { - const auto field_at_i = Kokkos::subview( - ver_emiss_fields_outputs, Kokkos::ALL(), ifield, Kokkos::ALL()); - Kokkos::deep_copy(field_at_i, vert_emis_output_[ifield]); - } -#endif - const_view_1d &col_latitudes = col_latitudes_; const_view_1d &d_sfc_alb_dir_vis = d_sfc_alb_dir_vis_; diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 32cfbc13ca1..96b06fc4553 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -5,9 +5,6 @@ #include #include -// For reading files -#include "readfiles/tracer_reader_utils.hpp" - // For calling MAM4 processes #include #include From c61e65ab937a620253d7150c85399a782f148d3e Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Wed, 30 Oct 2024 21:17:54 -0600 Subject: [PATCH 30/31] EAMxx: Add tracer_reader_utils.hpp to microphysics.hpp. --- .../physics/mam/eamxx_mam_microphysics_process_interface.cpp | 2 +- .../physics/mam/eamxx_mam_microphysics_process_interface.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp index 6ef28614315..b0ab232c255 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.cpp @@ -1,7 +1,7 @@ #include // impl namespace for some driver level functions for microphysics -#include "readfiles/tracer_reader_utils.hpp" + #include "readfiles/photo_table_utils.cpp" #include "physics/rrtmgp/shr_orb_mod_c2f.hpp" diff --git a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp index 96b06fc4553..6b1dd33dfaa 100644 --- a/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp +++ b/components/eamxx/src/physics/mam/eamxx_mam_microphysics_process_interface.hpp @@ -4,7 +4,7 @@ #include #include #include - +#include "readfiles/tracer_reader_utils.hpp" // For calling MAM4 processes #include #include From b49cf6737a525cc10b8c4bd90e649a0659fd2212 Mon Sep 17 00:00:00 2001 From: Oscar Diaz-Ibarra Date: Thu, 31 Oct 2024 15:52:39 -0600 Subject: [PATCH 31/31] EAMxx: Use the main branch of mam4xx. --- externals/mam4xx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/mam4xx b/externals/mam4xx index f7d07c5a359..aae46807bf5 160000 --- a/externals/mam4xx +++ b/externals/mam4xx @@ -1 +1 @@ -Subproject commit f7d07c5a3592a5e89101135681205e5e459f020f +Subproject commit aae46807bf58d6ffcbc6db620c27568450b2d040