diff --git a/components/eamxx/src/control/atmosphere_driver.cpp b/components/eamxx/src/control/atmosphere_driver.cpp index a5dacd856d7..4f2c0fc21b4 100644 --- a/components/eamxx/src/control/atmosphere_driver.cpp +++ b/components/eamxx/src/control/atmosphere_driver.cpp @@ -140,11 +140,10 @@ init_scorpio(const int atm_id) // Init scorpio right away, in case some class (atm procs, grids,...) // needs to source some data from NC files during construction, // before we start processing IC files. - EKAT_REQUIRE_MSG (!scorpio::is_eam_pio_subsystem_inited(), + EKAT_REQUIRE_MSG (!scorpio::is_subsystem_inited(), "Error! The PIO subsystem was alreday inited before the driver was constructed.\n" " This is an unexpected behavior. Please, contact developers.\n"); - MPI_Fint fcomm = MPI_Comm_c2f(m_atm_comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm,atm_id); + scorpio::init_subsystem(m_atm_comm,atm_id); // In CIME runs, gptl is already inited. In standalone runs, it might // not be, depending on what scorpio does. @@ -713,7 +712,7 @@ void AtmosphereDriver::initialize_output_managers () { } om.set_logger(m_atm_logger); for (const auto& it : m_atm_process_group->get_restart_extra_data()) { - om.add_global(it.first,*it.second); + om.add_global(it.first,it.second); } // Store the "Output Control" pl of the model restart as the "Checkpoint Control" for all other output streams @@ -909,7 +908,7 @@ void AtmosphereDriver::restart_model () } // Restart the num steps counter in the atm time stamp - int nsteps = scorpio::get_attribute(filename,"nsteps"); + int nsteps = scorpio::get_attribute(filename,"GLOBAL","nsteps"); m_current_ts.set_num_steps(nsteps); m_run_t0.set_num_steps(nsteps); @@ -917,18 +916,22 @@ void AtmosphereDriver::restart_model () const auto& name = it.first; auto& any = it.second; - - auto data = scorpio::get_any_attribute(filename,name); - EKAT_REQUIRE_MSG (any->content().type()==data.content().type(), - "Error! Type mismatch for restart global attribute.\n" - " - file name: " + filename + "\n" - " - att name: " + name + "\n" - " - expected type: " + any->content().type().name() + "\n" - " - actual type: " + data.content().type().name() + "\n" - "NOTE: the above names use type_info::name(), which returns an implementation defined string,\n" - " with no guarantees. In particular, the string can be identical for several types,\n" - " and/or change between invocations of the same program.\n"); - *any = data; + if (any.isType()) { + ekat::any_cast(any) = scorpio::get_attribute(filename,"GLOBAL",name); + } else if (any.isType()) { + ekat::any_cast(any) = scorpio::get_attribute(filename,"GLOBAL",name); + } else if (any.isType()) { + ekat::any_cast(any) = scorpio::get_attribute(filename,"GLOBAL",name); + } else if (any.isType()) { + ekat::any_cast(any) = scorpio::get_attribute(filename,"GLOBAL",name); + } else if (any.isType()) { + ekat::any_cast(any) = scorpio::get_attribute(filename,"GLOBAL",name); + } else { + EKAT_ERROR_MSG ( + "Error! Unrecognized/unsupported concrete type for restart extra data.\n" + " - extra data name : " + name + "\n" + " - extra data typeid: " + any.content().type().name() + "\n"); + } } m_atm_logger->info(" [EAMxx] restart_model ... done!"); @@ -1660,6 +1663,9 @@ void AtmosphereDriver::finalize ( /* inputs? */ ) { m_atm_process_group = nullptr; } + // Destroy iop + m_iop = nullptr; + // Destroy the buffer manager m_memory_buffer = nullptr; @@ -1681,9 +1687,10 @@ void AtmosphereDriver::finalize ( /* inputs? */ ) { finalize_gptl(); } - // Finalize scorpio - if (scorpio::is_eam_pio_subsystem_inited()) { - scorpio::eam_pio_finalize(); + // Finalize scorpio. Check, just in case we're calling finalize after + // an exception, thrown before the AD (and scorpio) was inited + if (scorpio::is_subsystem_inited()) { + scorpio::finalize_subsystem(); } m_atm_logger->info("[EAMxx] Finalize ... done!"); diff --git a/components/eamxx/src/control/intensive_observation_period.cpp b/components/eamxx/src/control/intensive_observation_period.cpp index a374c595102..ab01bfc7e3e 100644 --- a/components/eamxx/src/control/intensive_observation_period.cpp +++ b/components/eamxx/src/control/intensive_observation_period.cpp @@ -30,75 +30,6 @@ namespace ekat { namespace scream { namespace control { -// Helper functions for reading data from .nc file to support -// cases not currently supported in EAMxx scorpio interface. -namespace { -// Read the value of a dimensionless variable from file. -template -void read_dimensionless_variable_from_file(const std::string& filename, - const std::string& varname, - T* value) -{ - EKAT_REQUIRE_MSG(scorpio::has_variable(filename,varname), - "Error! IOP file does not have variable "+varname+".\n"); - - int ncid, varid, err1, err2; - bool was_open = scorpio::is_file_open_c2f(filename.c_str(),-1); - if (not was_open) { - scorpio::register_file(filename,scorpio::FileMode::Read); - } - ncid = scorpio::get_file_ncid_c2f (filename.c_str()); - err1 = PIOc_inq_varid(ncid,varname.c_str(),&varid); - EKAT_REQUIRE_MSG(err1==PIO_NOERR, - "Error! Something went wrong while retrieving variable id.\n" - " - filename : " + filename + "\n" - " - varname : " + varname + "\n" - " - pio error: " + std::to_string(err1) + "\n"); - - err2 = PIOc_get_var(ncid, varid, value); - EKAT_REQUIRE_MSG(err2==PIO_NOERR, - "Error! Something went wrong while retrieving variable.\n" - " - filename : " + filename + "\n" - " - varname : " + varname + "\n" - " - pio error: " + std::to_string(err2) + "\n"); - - if (not was_open) { - scorpio::eam_pio_closefile(filename); - } -} - -// Read variable with arbitrary number of dimensions from file. -template -void read_variable_from_file(const std::string& filename, - const std::string& varname, - const std::string& vartype, - const std::vector& dimnames, - const int time_idx, - T* data) -{ - EKAT_REQUIRE_MSG(scorpio::has_variable(filename,varname), - "Error! IOP file does not have variable "+varname+".\n"); - - // Compute total size of data to read - int data_size = 1; - for (auto dim : dimnames) { - const auto dim_len = scorpio::get_dimlen(filename, dim); - data_size *= dim_len; - } - - // Read into data - scorpio::register_file(filename, scorpio::FileMode::Read); - std::string io_decomp_tag = varname+","+filename; - scorpio::register_variable(filename, varname, varname, dimnames, vartype, io_decomp_tag); - std::vector dof_offsets(data_size); - std::iota(dof_offsets.begin(), dof_offsets.end(), 0); - scorpio::set_dof(filename, varname, dof_offsets.size(), dof_offsets.data()); - scorpio::set_decomp(filename); - scorpio::grid_read_data_array(filename, varname, time_idx, data, data_size); - scorpio::eam_pio_closefile(filename); -} -} - IntensiveObservationPeriod:: IntensiveObservationPeriod(const ekat::Comm& comm, const ekat::ParameterList& params, @@ -144,6 +75,13 @@ IntensiveObservationPeriod(const ekat::Comm& comm, initialize_iop_file(run_t0, model_nlevs); } +IntensiveObservationPeriod:: +~IntensiveObservationPeriod () +{ + const auto iop_file = m_params.get("iop_file"); + scorpio::release_file(iop_file); +} + void IntensiveObservationPeriod:: initialize_iop_file(const util::TimeStamp& run_t0, int model_nlevs) @@ -153,6 +91,11 @@ initialize_iop_file(const util::TimeStamp& run_t0, const auto iop_file = m_params.get("iop_file"); + // All the scorpio::has_var call can open the file on the fly, but since there + // are a lot of those calls, for performance reasons we just open it now. + // All the calls to register_file made on-the-fly inside has_var will be no-op. + scorpio::register_file(iop_file,scorpio::FileMode::Read); + // Lambda for allocating space and storing information for potential iop fields. // Inputs: // - varnames: Vector of possible variable names in the iop file. @@ -173,7 +116,7 @@ initialize_iop_file(const util::TimeStamp& run_t0, bool has_var = false; std::string file_varname = ""; for (auto varname : varnames) { - if (scorpio::has_variable(iop_file, varname)) { + if (scorpio::has_var(iop_file, varname)) { has_var = true; file_varname = varname; break; @@ -183,7 +126,7 @@ initialize_iop_file(const util::TimeStamp& run_t0, // Store if iop file has a different varname than the iop field if (iop_varname != file_varname) m_iop_file_varnames.insert({iop_varname, file_varname}); // Store if variable contains a surface value in iop file - if (scorpio::has_variable(iop_file, srf_varname)) { + if (scorpio::has_var(iop_file, srf_varname)) { m_iop_field_surface_varnames.insert({iop_varname, srf_varname}); } // Store that the IOP variable is found in the IOP file @@ -291,11 +234,12 @@ initialize_iop_file(const util::TimeStamp& run_t0, // Initialize time information int bdate; std::string bdate_name; - if (scorpio::has_variable(iop_file, "bdate")) bdate_name = "bdate"; - else if (scorpio::has_variable(iop_file, "basedate")) bdate_name = "basedate"; - else if (scorpio::has_variable(iop_file, "nbdate")) bdate_name = "nbdate"; + if (scorpio::has_var(iop_file, "bdate")) bdate_name = "bdate"; + else if (scorpio::has_var(iop_file, "basedate")) bdate_name = "basedate"; + else if (scorpio::has_var(iop_file, "nbdate")) bdate_name = "nbdate"; else EKAT_ERROR_MSG("Error! No valid name for bdate in "+iop_file+".\n"); - read_dimensionless_variable_from_file(iop_file, bdate_name, &bdate); + + scorpio::read_var(iop_file, bdate_name, &bdate); int yr=bdate/10000; int mo=(bdate/100) - yr*100; @@ -306,11 +250,15 @@ initialize_iop_file(const util::TimeStamp& run_t0, if (scorpio::has_dim(iop_file, "time")) time_dimname = "time"; else if (scorpio::has_dim(iop_file, "tsec")) time_dimname = "tsec"; else EKAT_ERROR_MSG("Error! No valid dimension for tsec in "+iop_file+".\n"); + const auto ntimes = scorpio::get_dimlen(iop_file, time_dimname); - m_time_info.iop_file_times_in_sec = - decltype(m_time_info.iop_file_times_in_sec)("iop_file_times", ntimes); - read_variable_from_file(iop_file, "tsec", "int", {time_dimname}, -1, - m_time_info.iop_file_times_in_sec.data()); + m_time_info.iop_file_times_in_sec = view_1d_host("iop_file_times", ntimes); + scorpio::read_var(iop_file,"tsec",m_time_info.iop_file_times_in_sec.data()); + + // From now on, when we read vars, "time" must be treated as unlimited, to avoid issues + if (not scorpio::is_dim_unlimited(iop_file,time_dimname)) { + scorpio::pretend_dim_is_unlimited(iop_file,time_dimname); + } // Check that lat/lon from iop file match the targets in parameters. Note that // longitude may be negtive in the iop file, we convert to positive before checking. @@ -318,8 +266,10 @@ initialize_iop_file(const util::TimeStamp& run_t0, const auto nlons = scorpio::get_dimlen(iop_file, "lon"); EKAT_REQUIRE_MSG(nlats==1 and nlons==1, "Error! IOP data file requires a single lat/lon pair.\n"); Real iop_file_lat, iop_file_lon; - read_variable_from_file(iop_file, "lat", "real", {"lat"}, -1, &iop_file_lat); - read_variable_from_file(iop_file, "lon", "real", {"lon"}, -1, &iop_file_lon); + + scorpio::read_var(iop_file,"lat",&iop_file_lat); + scorpio::read_var(iop_file,"lon",&iop_file_lon); + const Real rel_lat_err = std::fabs(iop_file_lat - m_params.get("target_latitude"))/ m_params.get("target_latitude"); const Real rel_lon_err = std::fabs(std::fmod(iop_file_lon + 360.0, 360.0)-m_params.get("target_longitude"))/ @@ -332,7 +282,7 @@ initialize_iop_file(const util::TimeStamp& run_t0, // Store iop file pressure as helper field with dimension lev+1. // Load the first lev entries from iop file, the lev+1 entry will // be set when reading iop data. - EKAT_REQUIRE_MSG(scorpio::has_variable(iop_file, "lev"), + EKAT_REQUIRE_MSG(scorpio::has_var(iop_file, "lev"), "Error! Using IOP file requires variable \"lev\".\n"); const auto file_levs = scorpio::get_dimlen(iop_file, "lev"); FieldIdentifier fid("iop_file_pressure", @@ -343,7 +293,8 @@ initialize_iop_file(const util::TimeStamp& run_t0, iop_file_pressure.get_header().get_alloc_properties().request_allocation(Pack::n); iop_file_pressure.allocate_view(); auto data = iop_file_pressure.get_view().data(); - read_variable_from_file(iop_file, "lev", "real", {"lev"}, -1, data); + scorpio::read_var(iop_file,"lev",data); + // Convert to pressure to millibar (file gives pressure in Pa) for (int ilev=0; ilev().data(); - read_variable_from_file(iop_file, "Ps", "real", {"lon","lat"}, iop_file_time_idx, ps_data); + + scorpio::read_var(iop_file,"Ps",ps_data,iop_file_time_idx); surface_pressure.sync_to_dev(); // Pre-process file pressures, store number of file levels @@ -676,7 +628,7 @@ read_iop_file_data (const util::TimeStamp& current_ts) if (field.rank()==0) { // For scalar data, read iop file variable directly into field data auto data = field.get_view().data(); - read_variable_from_file(iop_file, file_varname, "real", {"lon","lat"}, iop_file_time_idx, data); + scorpio::read_var(iop_file,file_varname,data,iop_file_time_idx); field.sync_to_dev(); } else if (field.rank()==1) { // Create temporary fields for reading iop file variables. We use @@ -693,7 +645,7 @@ read_iop_file_data (const util::TimeStamp& current_ts) // Read data from iop file. std::vector data(file_levs); - read_variable_from_file(iop_file, file_varname, "real", {"lon","lat","lev"}, iop_file_time_idx, data.data()); + scorpio::read_var(iop_file,file_varname,data.data(),iop_file_time_idx); // Copy first adjusted_file_levs-1 values to field auto iop_file_v_h = iop_file_field.get_view(); @@ -703,7 +655,7 @@ read_iop_file_data (const util::TimeStamp& current_ts) const auto has_srf = m_iop_field_surface_varnames.count(fname)>0; if (has_srf) { const auto srf_varname = m_iop_field_surface_varnames[fname]; - read_variable_from_file(iop_file, srf_varname, "real", {"lon","lat"}, iop_file_time_idx, &iop_file_v_h(adjusted_file_levs-1)); + scorpio::read_var(iop_file,srf_varname,&iop_file_v_h(adjusted_file_levs-1),iop_file_time_idx); } else { // No surface value exists, compute surface value const auto dx = iop_file_v_h(adjusted_file_levs-2) - iop_file_v_h(adjusted_file_levs-3); @@ -754,7 +706,7 @@ read_iop_file_data (const util::TimeStamp& current_ts) KOKKOS_LAMBDA (const int ilev) { iop_field_v(ilev) = iop_file_v(0); }); - Kokkos::parallel_for(Kokkos::RangePolicy<>(model_end-1, total_nlevs), + Kokkos::parallel_for(Kokkos::RangePolicy<>(model_end-1, total_nlevs), KOKKOS_LAMBDA (const int ilev) { iop_field_v(ilev) = iop_file_v(adjusted_file_levs-1); }); @@ -930,5 +882,3 @@ correct_temperature_and_water_vapor(const field_mgr_ptr field_mgr) } // namespace control } // namespace scream - - diff --git a/components/eamxx/src/control/intensive_observation_period.hpp b/components/eamxx/src/control/intensive_observation_period.hpp index 6c0b3640ffb..be3d389257b 100644 --- a/components/eamxx/src/control/intensive_observation_period.hpp +++ b/components/eamxx/src/control/intensive_observation_period.hpp @@ -55,7 +55,7 @@ class IntensiveObservationPeriod const Field& hybm); // Default destructor - ~IntensiveObservationPeriod() = default; + ~IntensiveObservationPeriod(); // Read data from IOP file and store internally. void read_iop_file_data(const util::TimeStamp& current_ts); diff --git a/components/eamxx/src/diagnostics/tests/field_at_level_tests.cpp b/components/eamxx/src/diagnostics/tests/field_at_level_tests.cpp index 44b4cd851b9..b7867c3ac72 100644 --- a/components/eamxx/src/diagnostics/tests/field_at_level_tests.cpp +++ b/components/eamxx/src/diagnostics/tests/field_at_level_tests.cpp @@ -74,7 +74,7 @@ TEST_CASE("field_at_level") using IPDF = std::uniform_int_distribution; IPDF ipdf (1,nlevs-2); - for (const std::string& lev_loc : {"model_top", "model_bot", "rand"}) { + for (const std::string lev_loc : {"model_top", "model_bot", "rand"}) { int lev; std::string lev_str; if (lev_loc=="bot") { diff --git a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp index b550348fa49..dfd116b0832 100644 --- a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp +++ b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp @@ -52,7 +52,8 @@ HommeDynamics::HommeDynamics (const ekat::Comm& comm, const ekat::ParameterList& // This class needs Homme's context, so register as a user HommeContextUser::singleton().add_user(); - auto homme_nsteps = std::make_shared(-1); + ekat::any homme_nsteps; + homme_nsteps.reset(-1); m_restart_extra_data["homme_nsteps"] = homme_nsteps; if (!is_parallel_inited_f90()) { @@ -516,7 +517,7 @@ void HommeDynamics::run_impl (const double dt) // Update nstep in the restart extra data, so it can be written to restart if needed. const auto& tl = c.get(); - auto& nstep = ekat::any_cast(*m_restart_extra_data["homme_nsteps"]); + auto& nstep = ekat::any_cast(m_restart_extra_data["homme_nsteps"]); nstep = tl.nstep; // Post process Homme's output, to produce what the rest of Atm expects @@ -955,7 +956,7 @@ void HommeDynamics::restart_homme_state () { auto& tl = c.get(); // For BFB restarts, set nstep counter in Homme's TimeLevel to match the restarted value. - const auto& nstep = ekat::any_ptr_cast(*m_restart_extra_data["homme_nsteps"]); + const auto& nstep = ekat::any_ptr_cast(m_restart_extra_data["homme_nsteps"]); tl.nstep = *nstep; set_homme_param("num_steps",*nstep); @@ -1135,7 +1136,7 @@ void HommeDynamics::initialize_homme_state () { const int n0 = tl.n0; const int n0_qdp = tl.n0_qdp; - ekat::any_cast(*m_restart_extra_data["homme_nsteps"]) = tl.nstep; + ekat::any_cast(m_restart_extra_data["homme_nsteps"]) = tl.nstep; const auto phis_dyn_view = m_helper_fields.at("phis_dyn").get_view(); const auto phi_int_view = m_helper_fields.at("phi_int_dyn").get_view(); diff --git a/components/eamxx/src/dynamics/homme/homme_grids_manager.cpp b/components/eamxx/src/dynamics/homme/homme_grids_manager.cpp index 55b236adee0..270f1ffbdda 100644 --- a/components/eamxx/src/dynamics/homme/homme_grids_manager.cpp +++ b/components/eamxx/src/dynamics/homme/homme_grids_manager.cpp @@ -155,21 +155,24 @@ void HommeGridsManager::build_dynamics_grid () { auto dg_dofs = dyn_grid->get_dofs_gids(); auto cg_dofs = dyn_grid->get_cg_dofs_gids(); auto elgpgp = dyn_grid->get_lid_to_idx_map(); + auto elgids = dyn_grid->get_partitioned_dim_gids (); auto lat = dyn_grid->create_geometry_data("lat",layout2d,rad); auto lon = dyn_grid->create_geometry_data("lon",layout2d,rad); auto dg_dofs_h = dg_dofs.get_view(); auto cg_dofs_h = cg_dofs.get_view(); auto elgpgp_h = elgpgp.get_view(); + auto elgids_h = elgids.get_view(); auto lat_h = lat.get_view(); auto lon_h = lon.get_view(); // Get (ie,igp,jgp,gid) data for each dof - get_dyn_grid_data_f90 (dg_dofs_h.data(),cg_dofs_h.data(),elgpgp_h.data(), lat_h.data(), lon_h.data()); + get_dyn_grid_data_f90 (dg_dofs_h.data(),cg_dofs_h.data(),elgpgp_h.data(),elgids_h.data(), lat_h.data(), lon_h.data()); dg_dofs.sync_to_dev(); cg_dofs.sync_to_dev(); elgpgp.sync_to_dev(); + elgids.sync_to_dev(); lat.sync_to_dev(); lon.sync_to_dev(); diff --git a/components/eamxx/src/dynamics/homme/interface/dyn_grid_mod.F90 b/components/eamxx/src/dynamics/homme/interface/dyn_grid_mod.F90 index 851659c7f12..cf8d955fe90 100644 --- a/components/eamxx/src/dynamics/homme/interface/dyn_grid_mod.F90 +++ b/components/eamxx/src/dynamics/homme/interface/dyn_grid_mod.F90 @@ -42,7 +42,7 @@ subroutine dyn_grid_init () end subroutine dyn_grid_init - subroutine get_my_dyn_data (dg_gids, cg_gids, elgpgp, lat, lon) + subroutine get_my_dyn_data (dg_gids, cg_gids, elgpgp, elgids, lat, lon) use iso_c_binding, only: c_int, c_double use dimensions_mod, only: nelemd, np use homme_context_mod, only: elem, par @@ -55,7 +55,7 @@ subroutine get_my_dyn_data (dg_gids, cg_gids, elgpgp, lat, lon) ! Inputs ! real(kind=c_double), intent(out) :: lat (:,:,:), lon(:,:,:) - integer(kind=c_int), intent(out) :: cg_gids (:), dg_gids(:), elgpgp(:,:) + integer(kind=c_int), intent(out) :: cg_gids (:), dg_gids(:), elgpgp(:,:), elgids(:) ! ! Local(s) ! @@ -80,6 +80,7 @@ subroutine get_my_dyn_data (dg_gids, cg_gids, elgpgp, lat, lon) call bndry_exchangeV(par,edge) do ie=1,nelemd call edgeVunpack_nlyr(edge,elem(ie)%desc,el_cg_gids(:,:,ie),1,0,1) + elgids(ie) = elem(ie)%GlobalId do ip=1,np do jp=1,np idof = (ie-1)*16+(jp-1)*4+ip diff --git a/components/eamxx/src/dynamics/homme/interface/homme_grid_mod.F90 b/components/eamxx/src/dynamics/homme/interface/homme_grid_mod.F90 index 6f11024627f..28dfed502f8 100644 --- a/components/eamxx/src/dynamics/homme/interface/homme_grid_mod.F90 +++ b/components/eamxx/src/dynamics/homme/interface/homme_grid_mod.F90 @@ -105,18 +105,18 @@ end subroutine finalize_geometry_f90 ! Note: dg_grid=.true. is to request dofs for a Discontinuous Galerkin grid, ! that is, corresponding edge dofs on bordering elems have different gids. ! If dg_grid=.false., the shared dofs have the same gid. - subroutine get_dyn_grid_data_f90 (dg_gids_ptr, cg_gids_ptr, elgpgp_ptr, lat_ptr, lon_ptr) bind(c) + subroutine get_dyn_grid_data_f90 (dg_gids_ptr, cg_gids_ptr, elgpgp_ptr, elgid_ptr, lat_ptr, lon_ptr) bind(c) use dimensions_mod, only: nelemd, np use dyn_grid_mod, only: get_my_dyn_data ! ! Input(s) ! - type (c_ptr), intent(in) :: dg_gids_ptr, cg_gids_ptr, elgpgp_ptr, lat_ptr, lon_ptr + type (c_ptr), intent(in) :: dg_gids_ptr, cg_gids_ptr, elgpgp_ptr, elgid_ptr, lat_ptr, lon_ptr ! ! Local(s) ! real(kind=c_double), pointer :: lat (:,:,:), lon(:,:,:) - integer(kind=c_int), pointer :: cg_gids (:), dg_gids(:), elgpgp(:,:) + integer(kind=c_int), pointer :: cg_gids (:), dg_gids(:), elgpgp(:,:), elgid(:) ! Sanity check call check_grids_inited(.true.) @@ -124,10 +124,11 @@ subroutine get_dyn_grid_data_f90 (dg_gids_ptr, cg_gids_ptr, elgpgp_ptr, lat_ptr, call c_f_pointer (dg_gids_ptr, dg_gids, [nelemd*np*np]) call c_f_pointer (cg_gids_ptr, cg_gids, [nelemd*np*np]) call c_f_pointer (elgpgp_ptr, elgpgp, [3,nelemd*np*np]) + call c_f_pointer (elgid_ptr, elgid, [nelemd]) call c_f_pointer (lat_ptr, lat, [np,np,nelemd]) call c_f_pointer (lon_ptr, lon, [np,np,nelemd]) - call get_my_dyn_data (dg_gids, cg_gids, elgpgp, lat, lon) + call get_my_dyn_data (dg_gids, cg_gids, elgpgp, elgid, lat, lon) end subroutine get_dyn_grid_data_f90 subroutine get_phys_grid_data_f90 (pg_type, gids_ptr, lat_ptr, lon_ptr, area_ptr) bind(c) diff --git a/components/eamxx/src/dynamics/homme/interface/scream_homme_interface.hpp b/components/eamxx/src/dynamics/homme/interface/scream_homme_interface.hpp index 8968fc49fb1..496472b54c0 100644 --- a/components/eamxx/src/dynamics/homme/interface/scream_homme_interface.hpp +++ b/components/eamxx/src/dynamics/homme/interface/scream_homme_interface.hpp @@ -65,6 +65,7 @@ int get_num_global_elems_f90 (); void get_dyn_grid_data_f90 (AbstractGrid::gid_type* const& dg_gids, AbstractGrid::gid_type* const& cg_gids, int* const& elgp, + AbstractGrid::gid_type* const& elgids, double* const& lat, double* const& lon); void get_phys_grid_data_f90 (const int& pg_type, AbstractGrid::gid_type* const& gids, diff --git a/components/eamxx/src/dynamics/homme/tests/dyn_grid_io.cpp b/components/eamxx/src/dynamics/homme/tests/dyn_grid_io.cpp index bb09daa1c49..ec1e709c7cd 100644 --- a/components/eamxx/src/dynamics/homme/tests/dyn_grid_io.cpp +++ b/components/eamxx/src/dynamics/homme/tests/dyn_grid_io.cpp @@ -41,8 +41,7 @@ TEST_CASE("dyn_grid_io") ekat::Comm comm(MPI_COMM_WORLD); // MPI communicator group used for I/O set as ekat object. // Initialize the pio_subsystem for this test: - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); // Init homme context if (!is_parallel_inited_f90()) { @@ -179,7 +178,7 @@ TEST_CASE("dyn_grid_io") } // Cleanup everything - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); Homme::Context::finalize_singleton(); cleanup_test_f90(); } diff --git a/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp b/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp index 40925c9edd1..832c030e97b 100644 --- a/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp +++ b/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp @@ -355,7 +355,7 @@ void Nudging::run_impl (const double dt) Real var_fill_value = constants::DefaultFillValue().value; // Query the helper field for the fill value, if not present use default if (f.get_header().has_extra_data("mask_value")) { - var_fill_value = f.get_header().get_extra_data("mask_value"); + var_fill_value = f.get_header().get_extra_data("mask_value"); } const int ncols = fl.dim(0); diff --git a/components/eamxx/src/physics/nudging/tests/create_map_file.cpp b/components/eamxx/src/physics/nudging/tests/create_map_file.cpp index 1a83abb5f7b..d8a67d84b27 100644 --- a/components/eamxx/src/physics/nudging/tests/create_map_file.cpp +++ b/components/eamxx/src/physics/nudging/tests/create_map_file.cpp @@ -7,7 +7,7 @@ TEST_CASE("create_map_file") using namespace scream; ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); // Add a dof in the middle of two coarse dofs const int ngdofs_src = 12; @@ -21,21 +21,15 @@ TEST_CASE("create_map_file") scorpio::register_file(filename, scorpio::FileMode::Write); - scorpio::register_dimension(filename, "n_a", "n_a", ngdofs_src, false); - scorpio::register_dimension(filename, "n_b", "n_b", ngdofs_tgt, false); - scorpio::register_dimension(filename, "n_s", "n_s", nnz, false); + scorpio::define_dim(filename, "n_a", ngdofs_src); + scorpio::define_dim(filename, "n_b", ngdofs_tgt); + scorpio::define_dim(filename, "n_s", nnz); - scorpio::register_variable(filename, "col", "col", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "row", "row", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "S", "S", "1", {"n_s"}, "double", "double", ""); + scorpio::define_var(filename, "col", {"n_s"}, "int"); + scorpio::define_var(filename, "row", {"n_s"}, "int"); + scorpio::define_var(filename, "S", {"n_s"}, "double"); - std::vector dofs(nnz); - std::iota(dofs.begin(),dofs.end(),0); - scorpio::set_dof(filename,"col",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"row",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"S", dofs.size(),dofs.data()); - - scorpio::eam_pio_enddef(filename); + scorpio::enddef(filename); std::vector col(nnz), row(nnz); std::vector S(nnz); @@ -54,10 +48,10 @@ TEST_CASE("create_map_file") S[ngdofs_src+2*i+1] = 0.5; } - scorpio::grid_write_data_array(filename,"row",row.data(),nnz); - scorpio::grid_write_data_array(filename,"col",col.data(),nnz); - scorpio::grid_write_data_array(filename,"S", S.data(), nnz); + scorpio::write_var(filename,"row",row.data()); + scorpio::write_var(filename,"col",col.data()); + scorpio::write_var(filename,"S", S.data()); - scorpio::eam_pio_closefile(filename); - scorpio::eam_pio_finalize(); + scorpio::release_file(filename); + scorpio::finalize_subsystem(); } diff --git a/components/eamxx/src/physics/nudging/tests/create_nudging_data.cpp b/components/eamxx/src/physics/nudging/tests/create_nudging_data.cpp index f5c8e6b4bda..000322b2374 100644 --- a/components/eamxx/src/physics/nudging/tests/create_nudging_data.cpp +++ b/components/eamxx/src/physics/nudging/tests/create_nudging_data.cpp @@ -20,7 +20,7 @@ TEST_CASE("create_nudging_data") { const auto t0 = get_t0(); // Initialize the pio_subsystem for this test: - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); // Create a grids manager const auto gm = create_gm(comm,ngcols,nlevs); @@ -56,5 +56,5 @@ TEST_CASE("create_nudging_data") { om2->finalize(); om3->finalize(); - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } diff --git a/components/eamxx/src/physics/nudging/tests/nudging_tests.cpp b/components/eamxx/src/physics/nudging/tests/nudging_tests.cpp index f5411205627..8870b4cce20 100644 --- a/components/eamxx/src/physics/nudging/tests/nudging_tests.cpp +++ b/components/eamxx/src/physics/nudging/tests/nudging_tests.cpp @@ -44,7 +44,7 @@ TEST_CASE("nudging_tests") { }; // Init scorpio - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); // A refined grid, with one extra node in between each of the coarse ones const int ngcols_fine = 2*ngcols_data - 1; @@ -462,5 +462,5 @@ TEST_CASE("nudging_tests") { } // Clean up scorpio - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } diff --git a/components/eamxx/src/physics/spa/spa_functions_impl.hpp b/components/eamxx/src/physics/spa/spa_functions_impl.hpp index d9547953d1e..287f69a9889 100644 --- a/components/eamxx/src/physics/spa/spa_functions_impl.hpp +++ b/components/eamxx/src/physics/spa/spa_functions_impl.hpp @@ -83,7 +83,7 @@ create_horiz_remapper ( const int ncols_data = scorpio::get_dimlen(spa_data_file,"ncol"); const int nswbands = scorpio::get_dimlen(spa_data_file,"swband"); const int nlwbands = scorpio::get_dimlen(spa_data_file,"lwband"); - scorpio::eam_pio_closefile(spa_data_file); + scorpio::release_file(spa_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) diff --git a/components/eamxx/src/physics/spa/tests/spa_one_to_one_remap_test.cpp b/components/eamxx/src/physics/spa/tests/spa_one_to_one_remap_test.cpp index 5579c3efa7e..71a7187ed38 100644 --- a/components/eamxx/src/physics/spa/tests/spa_one_to_one_remap_test.cpp +++ b/components/eamxx/src/physics/spa/tests/spa_one_to_one_remap_test.cpp @@ -25,7 +25,7 @@ TEST_CASE("spa_one_to_one_remap","spa") { // Set up the mpi communicator and init the pio subsystem ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); std::string spa_data_file = SCREAM_DATA_DIR "/init/spa_data_for_testing.nc"; @@ -107,7 +107,7 @@ TEST_CASE("spa_one_to_one_remap","spa") // All Done reader = nullptr; - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } // run_property // Some helper functions for the require statements: diff --git a/components/eamxx/src/physics/spa/tests/spa_read_data_from_file_test.cpp b/components/eamxx/src/physics/spa/tests/spa_read_data_from_file_test.cpp index 4c6533b5870..059781f40d8 100644 --- a/components/eamxx/src/physics/spa/tests/spa_read_data_from_file_test.cpp +++ b/components/eamxx/src/physics/spa/tests/spa_read_data_from_file_test.cpp @@ -26,7 +26,7 @@ TEST_CASE("spa_read_data","spa") // Set up the mpi communicator and init the pio subsystem ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); std::string spa_data_file = SCREAM_DATA_DIR "/init/spa_data_for_testing.nc"; std::string spa_remap_file = SCREAM_DATA_DIR "/init/spa_data_for_testing.nc"; @@ -104,7 +104,7 @@ TEST_CASE("spa_read_data","spa") // Clean up reader = nullptr; - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } // Some helper functions for the require statements: diff --git a/components/eamxx/src/share/atm_process/atmosphere_process.hpp b/components/eamxx/src/share/atm_process/atmosphere_process.hpp index da8f7c09deb..071ef906a75 100644 --- a/components/eamxx/src/share/atm_process/atmosphere_process.hpp +++ b/components/eamxx/src/share/atm_process/atmosphere_process.hpp @@ -78,7 +78,6 @@ class AtmosphereProcess : public ekat::enable_shared_from_this; template using strmap_t = std::map; @@ -268,8 +267,8 @@ class AtmosphereProcess : public ekat::enable_shared_from_this ekat::any // - the data_name is unique across the whole atm // The AD will take care of ensuring these are written/read to/from restart files. - const strmap_t& get_restart_extra_data () const { return m_restart_extra_data; } - strmap_t& get_restart_extra_data () { return m_restart_extra_data; } + const strmap_t& get_restart_extra_data () const { return m_restart_extra_data; } + strmap_t& get_restart_extra_data () { return m_restart_extra_data; } // Boolean that dictates whether or not the conservation checks are run for this process bool has_column_conservation_check () { return m_column_conservation_check_data.has_check; } @@ -477,7 +476,7 @@ class AtmosphereProcess : public ekat::enable_shared_from_this m_atm_logger; // Extra data needed for restart - strmap_t m_restart_extra_data; + strmap_t m_restart_extra_data; // Use at your own risk. Motivation: Free up device memory for a field that is // no longer used, such as a field read in the ICs used only to initialize diff --git a/components/eamxx/src/share/field/field_impl.hpp b/components/eamxx/src/share/field/field_impl.hpp index 1964622d005..f192d70bb68 100644 --- a/components/eamxx/src/share/field/field_impl.hpp +++ b/components/eamxx/src/share/field/field_impl.hpp @@ -465,10 +465,15 @@ update (const Field& x, const ST alpha, const ST beta) ST fill_val = constants::DefaultFillValue().value; if (x.get_header().has_extra_data("mask_value")) { - if (typeid(ST) == typeid(int)) { + + if (dt==DataType::IntType) { fill_val = x.get_header().get_extra_data("mask_value"); - } else { + } else if (dt==DataType::FloatType) { fill_val = x.get_header().get_extra_data("mask_value"); + } else if (dt==DataType::DoubleType) { + fill_val = x.get_header().get_extra_data("mask_value"); + } else { + EKAT_ERROR_MSG ("Error! Unrecognized/unsupported field data type in Field::update.\n"); } } diff --git a/components/eamxx/src/share/grid/abstract_grid.cpp b/components/eamxx/src/share/grid/abstract_grid.cpp index 3e6b9fd0a0c..0cc83822c23 100644 --- a/components/eamxx/src/share/grid/abstract_grid.cpp +++ b/components/eamxx/src/share/grid/abstract_grid.cpp @@ -223,6 +223,26 @@ get_global_max_dof_gid () const ->gid_type return m_global_max_dof_gid; } +auto AbstractGrid:: +get_global_min_partitioned_dim_gid () const ->gid_type +{ + // Lazy calculation + if (m_global_min_partitioned_dim_gid==std::numeric_limits::max()) { + m_global_min_partitioned_dim_gid = field_min(m_partitioned_dim_gids,&get_comm()); + } + return m_global_min_partitioned_dim_gid; +} + +auto AbstractGrid:: +get_global_max_partitioned_dim_gid () const ->gid_type +{ + // Lazy calculation + if (m_global_max_partitioned_dim_gid==-std::numeric_limits::max()) { + m_global_max_partitioned_dim_gid = field_max(m_partitioned_dim_gids,&get_comm()); + } + return m_global_max_partitioned_dim_gid; +} + Field AbstractGrid::get_dofs_gids () const { return m_dofs_gids.get_const(); @@ -233,6 +253,16 @@ AbstractGrid::get_dofs_gids () { return m_dofs_gids; } +Field +AbstractGrid::get_partitioned_dim_gids () { + return m_partitioned_dim_gids; +} + +Field +AbstractGrid::get_partitioned_dim_gids () const { + return m_partitioned_dim_gids.get_const(); +} + Field AbstractGrid::get_lid_to_idx_map () const { return m_lid_to_idx.get_const(); @@ -521,8 +551,10 @@ void AbstractGrid::copy_data (const AbstractGrid& src, const bool shallow) { if (shallow) { m_dofs_gids = src.m_dofs_gids; + m_partitioned_dim_gids = src.m_partitioned_dim_gids; } else { m_dofs_gids = src.m_dofs_gids.clone(); + m_partitioned_dim_gids = src.m_partitioned_dim_gids.clone(); } if (shallow) { diff --git a/components/eamxx/src/share/grid/abstract_grid.hpp b/components/eamxx/src/share/grid/abstract_grid.hpp index 00abba1571a..2098fd49e94 100644 --- a/components/eamxx/src/share/grid/abstract_grid.hpp +++ b/components/eamxx/src/share/grid/abstract_grid.hpp @@ -112,11 +112,19 @@ class AbstractGrid : public ekat::enable_shared_from_this gid_type get_num_global_dofs () const { return m_num_global_dofs; } gid_type get_global_min_dof_gid () const; gid_type get_global_max_dof_gid () const; + gid_type get_global_min_partitioned_dim_gid () const; + gid_type get_global_max_partitioned_dim_gid () const; // Get a Field storing 1d data (the dof gids) Field get_dofs_gids () const; Field get_dofs_gids (); + // Get Field storing the gids that this process owns along the partitioned dim + // NOTE: for some grids, this is the same as get_dofs_gids. The SEGrid is a counterexample: + // the dofs are the GLL dofs, but the partitioned dim is the element dimension + Field get_partitioned_dim_gids (); + Field get_partitioned_dim_gids () const; + // Get a Field storing 2d data, where (i,j) entry contains the j-th coordinate of // the i-th dof in the native dof layout. Const verison returns a read-only field Field get_lid_to_idx_map () const; @@ -180,10 +188,8 @@ class AbstractGrid : public ekat::enable_shared_from_this virtual bool check_valid_lid_to_idx () const { return true; } void reset_field_tag_name (const FieldTag t, const std::string& s) { m_special_tag_names[t] = s; } - std::string get_dim_name (const FieldLayout& lt, const int idim) const { - const auto t = lt.tag(idim); - return m_special_tag_names.count(t)==1 ? m_special_tag_names.at(t) : lt.names()[idim]; - } + bool has_special_tag_name (const FieldTag t) const { return m_special_tag_names.count(t)==1; } + std::string get_special_tag_name (const FieldTag t) const { return m_special_tag_names.at(t); } // This member is used mostly by IO: if a field exists on multiple grids // with the same name, IO can use this as a suffix to diambiguate the fields in @@ -203,7 +209,6 @@ class AbstractGrid : public ekat::enable_shared_from_this // since it calls get_2d_scalar_layout. void create_dof_fields (const int scalar2d_layout_rank); - // The grid name and type GridType m_type; std::string m_name; @@ -222,9 +227,15 @@ class AbstractGrid : public ekat::enable_shared_from_this // The global ID of each dof Field m_dofs_gids; + // The global ID of the owned entries of the partitioned dimension (if any) + Field m_partitioned_dim_gids; + // The max/min dof GID across all ranks. Mutable, to allow for lazy calculation mutable gid_type m_global_min_dof_gid = std::numeric_limits::max(); mutable gid_type m_global_max_dof_gid = -std::numeric_limits::max(); + // Same as above, but for partitioned dim gids + mutable gid_type m_global_min_partitioned_dim_gid = std::numeric_limits::max(); + mutable gid_type m_global_max_partitioned_dim_gid = -std::numeric_limits::max(); // The fcn is_unique is expensive, so we lazy init this at the first call. mutable bool m_is_unique; diff --git a/components/eamxx/src/share/grid/mesh_free_grids_manager.cpp b/components/eamxx/src/share/grid/mesh_free_grids_manager.cpp index d1525bfec02..e0f78e10cf0 100644 --- a/components/eamxx/src/share/grid/mesh_free_grids_manager.cpp +++ b/components/eamxx/src/share/grid/mesh_free_grids_manager.cpp @@ -70,10 +70,12 @@ build_se_grid (const std::string& name, ekat::ParameterList& params) se_grid->setSelfPointer(se_grid); // Set up the degrees of freedom. - auto dof_gids = se_grid->get_dofs_gids(); - auto lid2idx = se_grid->get_lid_to_idx_map(); + auto dof_gids = se_grid->get_dofs_gids(); + auto elem_gids = se_grid->get_partitioned_dim_gids(); + auto lid2idx = se_grid->get_lid_to_idx_map(); auto host_dofs = dof_gids.template get_view(); + auto host_elems = elem_gids.template get_view(); auto host_lid2idx = lid2idx.template get_view(); // Count unique local dofs. On all elems except the very last one (on rank N), @@ -82,6 +84,7 @@ build_se_grid (const std::string& name, ekat::ParameterList& params) int offset = num_local_dofs*m_comm.rank(); for (int ie = 0; ie < num_local_elems; ++ie) { + host_elems[ie] = ie + num_local_elems*m_comm.rank(); for (int igp = 0; igp < num_gp; ++igp) { for (int jgp = 0; jgp < num_gp; ++jgp) { int idof = ie*num_gp*num_gp + igp*num_gp + jgp; @@ -96,6 +99,7 @@ build_se_grid (const std::string& name, ekat::ParameterList& params) // Sync to device dof_gids.sync_to_dev(); + elem_gids.sync_to_dev(); lid2idx.sync_to_dev(); se_grid->m_short_name = "se"; @@ -162,13 +166,13 @@ add_geo_data (const nonconstgrid_ptr_type& grid) const lev.sync_to_dev(); } else if (geo_data_source=="IC_FILE"){ const auto& filename = m_params.get("ic_filename"); - if (scorpio::has_variable(filename,"lat") && - scorpio::has_variable(filename,"lon")) { + if (scorpio::has_var(filename,"lat") && + scorpio::has_var(filename,"lon")) { load_lat_lon(grid,filename); } - if (scorpio::has_variable(filename,"hyam") && - scorpio::has_variable(filename,"hybm")) { + if (scorpio::has_var(filename,"hyam") && + scorpio::has_var(filename,"hybm")) { load_vertical_coordinates(grid,filename); } } diff --git a/components/eamxx/src/share/grid/point_grid.cpp b/components/eamxx/src/share/grid/point_grid.cpp index 8cf73e21f2a..7346020fa3e 100644 --- a/components/eamxx/src/share/grid/point_grid.cpp +++ b/components/eamxx/src/share/grid/point_grid.cpp @@ -15,6 +15,9 @@ PointGrid (const std::string& grid_name, { create_dof_fields (get_2d_scalar_layout().rank()); + // The partitioned dim is the COL dim, which concide with the dofs + m_partitioned_dim_gids = m_dofs_gids; + // The lid->idx map is the identity map. auto lid2idx = get_lid_to_idx_map(); auto h_lid_to_idx = lid2idx.get_view(); diff --git a/components/eamxx/src/share/grid/remap/horiz_interp_remapper_data.cpp b/components/eamxx/src/share/grid/remap/horiz_interp_remapper_data.cpp index d44fc6aa2f0..b26501a499b 100644 --- a/components/eamxx/src/share/grid/remap/horiz_interp_remapper_data.cpp +++ b/components/eamxx/src/share/grid/remap/horiz_interp_remapper_data.cpp @@ -40,40 +40,19 @@ get_my_triplets (const std::string& map_file) const // 1. Load the map file chunking it evenly across all ranks scorpio::register_file(map_file,scorpio::FileMode::Read); - // 1.1 Create a "helper" grid, with as many dofs as the number - // of triplets in the map file, and divided linearly across ranks - const int ngweights = scorpio::get_dimlen(map_file,"n_s"); - int nlweights = ngweights / comm.size(); - if (comm.rank() < (ngweights % comm.size())) { - nlweights += 1; - } - - gid_type offset = nlweights; - comm.scan(&offset,1,MPI_SUM); - offset -= nlweights; // scan is inclusive, but we need exclusive + // Inform scorpio that we will provide "int" pointers for row/col indices + scorpio::change_var_dtype(map_file,"row","int"); + scorpio::change_var_dtype(map_file,"col","int"); - // Create a unique decomp tag, which ensures all refining remappers have - // their own decomposition - static int tag_counter = 0; - const std::string int_decomp_tag = "RR::gmtg,int,grid-idx=" + std::to_string(tag_counter++); - const std::string real_decomp_tag = "RR::gmtg,real,grid-idx=" + std::to_string(tag_counter++); + // Decompose n_s dim linearly across ranks + scorpio::set_dim_decomp (map_file,"n_s"); + int nlweights = scorpio::get_dimlen_local(map_file,"n_s"); - // 1.2 Read a chunk of triplets col indices + // 1.1 Read a chunk of triplets col indices std::vector cols(nlweights); std::vector rows(nlweights); std::vector S(nlweights); - scorpio::register_variable(map_file, "col", "col", {"n_s"}, "int", int_decomp_tag); - scorpio::register_variable(map_file, "row", "row", {"n_s"}, "int", int_decomp_tag); - scorpio::register_variable(map_file, "S", "S", {"n_s"}, "real", real_decomp_tag); - - std::vector dofs_offsets(nlweights); - std::iota(dofs_offsets.begin(),dofs_offsets.end(),offset); - scorpio::set_dof(map_file,"col",nlweights,dofs_offsets.data()); - scorpio::set_dof(map_file,"row",nlweights,dofs_offsets.data()); - scorpio::set_dof(map_file,"S" ,nlweights,dofs_offsets.data()); - scorpio::set_decomp(map_file); - // Figure out if we are reading the right map, that is: // - n_a or n_b matches the fine grid ncols // - the map "direction" (fine->coarse or coarse->fine) matches m_type @@ -95,13 +74,13 @@ get_my_triplets (const std::string& map_file) const " - fine grid ncols: " + std::to_string(ncols_fine) + "\n" " - remapper type: " + std::string(type==InterpType::Refine ? "refine" : "coarsen") + "\n"); - scorpio::grid_read_data_array(map_file,"col",-1,cols.data(),nlweights); - scorpio::grid_read_data_array(map_file,"row",-1,rows.data(),nlweights); - scorpio::grid_read_data_array(map_file,"S" ,-1,S.data(),nlweights); + scorpio::read_var(map_file,"col",cols.data()); + scorpio::read_var(map_file,"row",rows.data()); + scorpio::read_var(map_file,"S" ,S.data()); - scorpio::eam_pio_closefile(map_file); + scorpio::release_file(map_file); - // 1.3 Dofs in grid are likely 0-based, while row/col ids in map file + // 1.2 Dofs in grid are likely 0-based, while row/col ids in map file // are likely 1-based. To match dofs, we need to offset the row/cols // ids we just read in. int map_file_min_row = std::numeric_limits::max(); diff --git a/components/eamxx/src/share/grid/remap/vertical_remapper.cpp b/components/eamxx/src/share/grid/remap/vertical_remapper.cpp index 78d18e086a6..c899bef26dc 100644 --- a/components/eamxx/src/share/grid/remap/vertical_remapper.cpp +++ b/components/eamxx/src/share/grid/remap/vertical_remapper.cpp @@ -70,7 +70,7 @@ VerticalRemapper (const grid_ptr_type& src_grid, // Add tgt pressure levels to the tgt grid tgt_grid->set_geometry_data(m_remap_pres); - scorpio::eam_pio_closefile(map_file); + scorpio::release_file(map_file); } FieldLayout VerticalRemapper:: @@ -154,13 +154,7 @@ set_pressure_levels(const std::string& map_file) auto remap_pres_scal = m_remap_pres.get_view(); - std::vector dofs_offsets(m_num_remap_levs); - std::iota(dofs_offsets.begin(),dofs_offsets.end(),0); - const std::string decomp_tag = "VR::spl,nlev=" + std::to_string(m_num_remap_levs) + ",file-idx=" + std::to_string(file2idx[map_file]); - scorpio::register_variable(map_file, "p_levs", "p_levs", {"lev"}, "real", decomp_tag); - scorpio::set_dof(map_file,"p_levs",m_num_remap_levs,dofs_offsets.data()); - scorpio::set_decomp(map_file); - scorpio::grid_read_data_array(map_file,"p_levs",-1,remap_pres_scal.data(),remap_pres_scal.size()); + scorpio::read_var(map_file,"p_levs",remap_pres_scal.data()); m_remap_pres.sync_to_dev(); } diff --git a/components/eamxx/src/share/grid/se_grid.cpp b/components/eamxx/src/share/grid/se_grid.cpp index 741bdb4e640..284191a69da 100644 --- a/components/eamxx/src/share/grid/se_grid.cpp +++ b/components/eamxx/src/share/grid/se_grid.cpp @@ -1,6 +1,7 @@ #include "share/grid/se_grid.hpp" +#include "share/field/field_utils.hpp" -#include "ekat/kokkos//ekat_subview_utils.hpp" +#include namespace scream { @@ -29,6 +30,9 @@ SEGrid (const std::string& grid_name, const auto units = ekat::units::Units::nondimensional(); m_cg_dofs_gids = Field(FieldIdentifier("cg_gids",FieldLayout({CMP},{get_num_local_dofs()}),units,this->name(),DataType::IntType)); m_cg_dofs_gids.allocate_view(); + + m_partitioned_dim_gids = Field(FieldIdentifier("el_gids",FieldLayout({EL},{m_num_local_elem}),units,this->name(),DataType::IntType)); + m_partitioned_dim_gids.allocate_view(); } FieldLayout @@ -127,20 +131,6 @@ SEGrid::get_3d_tensor_layout (const bool midpoints, return fl.rename_dims(m_special_tag_names); } -Field SEGrid::get_cg_dofs_gids () -{ - EKAT_REQUIRE_MSG (m_cg_dofs_gids.is_allocated(), - "Error! CG dofs have not been created yet.\n"); - return m_cg_dofs_gids; -} - -Field SEGrid::get_cg_dofs_gids () const -{ - EKAT_REQUIRE_MSG (m_cg_dofs_gids.is_allocated(), - "Error! CG dofs have not been created yet.\n"); - return m_cg_dofs_gids.get_const(); -} - std::shared_ptr SEGrid::clone (const std::string& clone_name, const bool shallow) const { auto grid = std::make_shared(clone_name,m_num_local_elem,m_num_gp,get_num_vertical_levels(),get_comm()); diff --git a/components/eamxx/src/share/grid/se_grid.hpp b/components/eamxx/src/share/grid/se_grid.hpp index 79274e96bca..87c86887fe3 100644 --- a/components/eamxx/src/share/grid/se_grid.hpp +++ b/components/eamxx/src/share/grid/se_grid.hpp @@ -43,8 +43,8 @@ class SEGrid : public AbstractGrid } // Retrieve list of the CG grid dofs. Const version returns a read-only field - Field get_cg_dofs_gids (); - Field get_cg_dofs_gids () const; + Field get_cg_dofs_gids () { return m_cg_dofs_gids; } + Field get_cg_dofs_gids () const { return m_cg_dofs_gids.get_const(); } std::shared_ptr clone (const std::string& clone_name, const bool shallow) const override; @@ -59,6 +59,10 @@ class SEGrid : public AbstractGrid int m_num_global_elem; int m_num_gp; + // The max/min elem GID across all ranks. Mutable, to allow for lazy calculation + mutable gid_type m_global_min_elem_gid = std::numeric_limits::max(); + mutable gid_type m_global_max_elem_gid = -std::numeric_limits::max(); + // The dofs gids for a CG version of this grid Field m_cg_dofs_gids; }; diff --git a/components/eamxx/src/share/io/CMakeLists.txt b/components/eamxx/src/share/io/CMakeLists.txt index 8866e294495..afb0ef888db 100644 --- a/components/eamxx/src/share/io/CMakeLists.txt +++ b/components/eamxx/src/share/io/CMakeLists.txt @@ -1,38 +1,65 @@ -set(SCREAM_SCORPIO_SRCS - scream_scorpio_interface.F90 +######################################### +# SCORPIO interface library # +######################################### + +# This small lib contains some interfaces to scorpio, which are formulated +# in terms of names rather than IDs. Internally, we store a list of +# small structs, holding handlers to PIO data and extra metadata, which +# allows to perform sanity checks and print more helpful messages + +add_library(scream_scorpio_interface + scream_scorpio_types.cpp scream_scorpio_interface.cpp - scream_scorpio_interface_iso_c2f.F90 - scream_output_manager.cpp - scorpio_input.cpp - scorpio_output.cpp - scream_io_utils.cpp ) - -# Create io lib -add_library(scream_io ${SCREAM_SCORPIO_SRCS}) -set_target_properties(scream_io PROPERTIES - Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/modules +target_link_libraries(scream_scorpio_interface PUBLIC ekat) +target_link_libraries(scream_scorpio_interface PRIVATE pioc) +target_include_directories(scream_scorpio_interface PUBLIC + ${SCREAM_BIN_DIR}/src # For scream_config.h ) -target_include_directories(scream_io PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/modules) if (DEFINED ENV{ADIOS2_ROOT}) - target_include_directories(scream_io PRIVATE $ENV{ADIOS2_ROOT}/include) + target_include_directories(scream_scorpio_interface PRIVATE $ENV{ADIOS2_ROOT}/include) endif () if (SCORPIO_Fortran_INCLUDE_DIRS) - target_include_directories(scream_io PUBLIC ${SCORPIO_Fortran_INCLUDE_DIRS}) + target_include_directories(scream_scorpio_interface PUBLIC ${SCORPIO_Fortran_INCLUDE_DIRS}) endif () if (SCORPIO_C_INCLUDE_DIRS) - target_include_directories(scream_io PUBLIC ${SCORPIO_C_INCLUDE_DIRS}) + target_include_directories(scream_scorpio_interface PUBLIC ${SCORPIO_C_INCLUDE_DIRS}) endif () -target_link_libraries(scream_io PUBLIC scream_share piof pioc) - if (SCREAM_CIME_BUILD) - target_link_libraries(scream_io PUBLIC csm_share) + # Add interface to E3SM shr lib (to retrieve PIO subsystem info) + target_sources (scream_scorpio_interface PRIVATE + scream_shr_interface_c2f.F90 + ) + target_compile_definitions (scream_scorpio_interface PRIVATE SCREAM_CIME_BUILD) + set_target_properties(scream_scorpio_interface PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/modules + ) + target_include_directories(scream_scorpio_interface PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/modules) + target_link_libraries(scream_scorpio_interface PUBLIC csm_share) endif() +################################## +# EAMxx I/O library # +################################## + +# This library allows for a simple(r) interaction between EAMxx +# data structures (such as grids, fields, remappers,...) and +# the scorpio interface library + +# Create io lib +add_library(scream_io + scream_output_manager.cpp + scorpio_input.cpp + scorpio_output.cpp + scream_io_utils.cpp +) + +target_link_libraries(scream_io PUBLIC scream_share scream_scorpio_interface) + if (NOT SCREAM_LIB_ONLY) add_subdirectory(tests) endif() diff --git a/components/eamxx/src/share/io/scorpio_input.cpp b/components/eamxx/src/share/io/scorpio_input.cpp index 0c04a612a02..e8a510130e0 100644 --- a/components/eamxx/src/share/io/scorpio_input.cpp +++ b/components/eamxx/src/share/io/scorpio_input.cpp @@ -220,7 +220,7 @@ void AtmosphereInput::read_variables (const int time_index) // Read the data auto v1d = m_host_views_1d.at(name); - scorpio::grid_read_data_array(m_filename,name,time_index,v1d.data(),v1d.size()); + scorpio::read_var(m_filename,name,v1d.data(),time_index); // If we have a field manager, make sure the data is correctly // synced to both host and device views of the field. @@ -335,7 +335,7 @@ void AtmosphereInput::read_variables (const int time_index) /* ---------------------------------------------------------- */ void AtmosphereInput::finalize() { - scorpio::eam_pio_closefile(m_filename); + scorpio::release_file(m_filename); m_field_mgr = nullptr; m_io_grid = nullptr; @@ -351,51 +351,60 @@ void AtmosphereInput::finalize() void AtmosphereInput::init_scorpio_structures() { std::string iotype_str = m_params.get("iotype", "default"); - int iotype = scorpio::str2iotype(iotype_str); + auto iotype = scorpio::str2iotype(iotype_str); scorpio::register_file(m_filename,scorpio::Read,iotype); - // Register variables with netCDF file. - register_variables(); - set_degrees_of_freedom(); - - // Finish the definition phase for this file. - scorpio::set_decomp (m_filename); -} - -/* ---------------------------------------------------------- */ -void AtmosphereInput::register_variables() -{ - // Register each variable in IO stream with the SCORPIO interface. - // This allows SCORPIO to lookup vars in the nc file with the correct - // dof decomposition across different ranks. + // Some input files have the "time" dimension as non-unlimited. This messes up our + // scorpio interface. To avoid trouble, if a dim called 'time' is present we + // treat it as unlimited, even though it isn't. + if (scorpio::has_dim(m_filename,"time") and not scorpio::is_dim_unlimited(m_filename,"time")) { + scorpio::pretend_dim_is_unlimited(m_filename,"time"); + } - // Cycle through all fields - const auto& fp_precision = "real"; + // Check variables are in the input file for (auto const& name : m_fields_names) { - // Determine the IO-decomp and construct a vector of dimension ids for this variable: const auto& layout = m_layouts.at(name); + + // Determine the IO-decomp and construct a vector of dimension ids for this variable: auto vec_of_dims = get_vec_of_dims(layout); - auto io_decomp_tag = get_io_decomp(layout); - for (size_t i=0; iget_partitioned_dim_tag()==layout.tags()[i]; - auto dimlen = partitioned ? m_io_grid->get_partitioned_dim_global_size() : layout.dims()[i]; - scorpio::register_dimension(m_filename, vec_of_dims[i], vec_of_dims[i], dimlen, partitioned); + // Check that the variable is in the file. + EKAT_REQUIRE_MSG (scorpio::has_var(m_filename,name), + "Error! Input file does not store a required variable.\n" + " - filename: " + m_filename + "\n" + " - varname : " + name + "\n"); + + const auto& var = scorpio::get_var(m_filename,name); + EKAT_REQUIRE_MSG (var.dim_names()==vec_of_dims, + "Error! Layout mismatch for input file variable.\n" + " - filename: " + m_filename + "\n" + " - varname : " + name + "\n" + " - expected dims : " + ekat::join(vec_of_dims,",") + "\n" + " - dims from file: " + ekat::join(var.dim_names(),",") + "\n"); + + // Check that all dims for this var match the ones on file + for (int i=0; iget_partitioned_dim_tag()==layout.tag(i); + const int eamxx_len = partitioned ? m_io_grid->get_partitioned_dim_global_size() + : layout.dim(i); + EKAT_REQUIRE_MSG (eamxx_len==file_len, + "Error! Dimension mismatch for input file variable.\n" + " - filename : " + m_filename + "\n" + " - varname : " + name + "\n" + " - var dims : " + ekat::join(vec_of_dims,",") + "\n" + " - dim name : " + vec_of_dims[i] + "\n" + " - expected extent : " + std::to_string(eamxx_len) + "\n" + " - extent from file: " + std::to_string(file_len) + "\n"); } - // TODO: Reverse order of dimensions to match flip between C++ -> F90 -> PIO, - // may need to delete this line when switching to full C++/C implementation. - std::reverse(vec_of_dims.begin(),vec_of_dims.end()); - - // Register the variable - // TODO Need to change dtype to allow for other variables. - // Currently the field_manager only stores Real variables so it is not an issue, - // but in the future if non-Real variables are added we will want to accomodate that. - //TODO: Should be able to simply inquire from the netCDF the dimensions for each variable. - scorpio::register_variable(m_filename, name, name, - vec_of_dims, fp_precision, io_decomp_tag); + // Ensure that we can read the var using Real data type + scorpio::change_var_dtype (m_filename,name,"real"); } + + // Set decompositions for the variables + set_decompositions(); } /* ---------------------------------------------------------- */ @@ -408,121 +417,56 @@ AtmosphereInput::get_vec_of_dims(const FieldLayout& layout) std::vector dims_names; dims_names.reserve(layout.rank()); for (int i=0; iget_dim_name(layout,i)); - if (dims_names.back()=="dim") { - dims_names.back() += std::to_string(layout.dim(i)); - } - } + const auto t = layout.tag(i); + std::string n = m_io_grid->has_special_tag_name(t) + ? m_io_grid->get_special_tag_name(t) + : layout.names()[i]; - return dims_names; -} + // If t==CMP, and the name stored in the layout is the default ("dim"), + // we append also the extent, to allow different vector dims in the file + n += n=="dim" ? std::to_string(layout.dim(i)) : ""; -/* ---------------------------------------------------------- */ -std::string AtmosphereInput:: -get_io_decomp(const FieldLayout& layout) -{ - std::string decomp_tag = "dt=real,grid-idx=" + std::to_string(m_io_grid->get_unique_grid_id()) + ",layout="; - - std::vector range(layout.rank()); - std::iota(range.begin(),range.end(),0); - auto tag_and_dim = [&](int i) { - return m_io_grid->get_dim_name(layout,i) + - std::to_string(layout.dim(i)); - }; - - decomp_tag += ekat::join (range, tag_and_dim,"-"); + dims_names.push_back(n); + } - return decomp_tag; + return dims_names; } /* ---------------------------------------------------------- */ -void AtmosphereInput::set_degrees_of_freedom() -{ - // For each field, tell PIO the offset of each DOF to be read. - // Here, offset is meant in the *global* array in the nc file. - for (auto const& name : m_fields_names) { - auto var_dof = get_var_dof_offsets(m_layouts.at(name)); - scorpio::set_dof(m_filename,name,var_dof.size(),var_dof.data()); - } -} // set_degrees_of_freedom - -/* ---------------------------------------------------------- */ -std::vector -AtmosphereInput::get_var_dof_offsets(const FieldLayout& layout) +void AtmosphereInput::set_decompositions() { using namespace ShortFieldTagsNames; - // Precompute this *before* the early return, since it involves collectives. - // If one rank owns zero cols, and returns prematurely, the others will be left waiting. - AbstractGrid::gid_type min_gid; - if (layout.has_tag(COL) or layout.has_tag(EL)) { - min_gid = m_io_grid->get_global_min_dof_gid(); - } - - // It may be that this MPI ranks owns no chunk of the field - if (layout.size()==0) { - return {}; - } + // First, check if any of the vars is indeed partitioned + const auto decomp_tag = m_io_grid->get_partitioned_dim_tag(); - std::vector var_dof(layout.size()); - - // Gather the offsets of the dofs of this variable w.r.t. the *global* array. - // Since we order the global array based on dof gid, and we *assume* (we actually - // check this during set_grid) that the grid global gids are in the interval - // [gid_0, gid_0+num_global_dofs), the offset is simply given by - // (dof_gid-gid_0)*column_size (for partitioned arrays). - // NOTE: a "dof" in the grid object is not the same as a "dof" in scorpio. - // For a SEGrid 3d vector field with (MPI local) layout (nelem,2,np,np,nlev), - // scorpio sees nelem*2*np*np*nlev dofs, while the SE grid sees nelem*np*np dofs. - // All we need to do in this routine is to compute the offset of all the entries - // of the MPI-local array w.r.t. the global array. So long as the offsets are in - // the same order as the corresponding entry in the data to be read/written, we're good. - auto dofs_h = m_io_grid->get_dofs_gids().get_view(); - if (layout.has_tag(COL)) { - const int num_cols = m_io_grid->get_num_local_dofs(); - - // Note: col_size might be *larger* than the number of vertical levels, or even smaller. - // E.g., (ncols,2,nlevs), or (ncols,2) respectively. - scorpio::offset_t col_size = layout.size() / num_cols; - - for (int icol=0; icolget_2d_scalar_layout(); - const int num_my_elems = layout2d.dim(0); - const int ngp = layout2d.dim(1); - const int num_cols = num_my_elems*ngp*ngp; - - // Note: col_size might be *larger* than the number of vertical levels, or even smaller. - // E.g., (ncols,2,nlevs), or (ncols,2) respectively. - scorpio::offset_t col_size = layout.size() / num_cols; - - for (int ie=0,icol=0; ieget_partitioned_dim_local_size(); + std::string decomp_dim = m_io_grid->has_special_tag_name(decomp_tag) + ? m_io_grid->get_special_tag_name(decomp_tag) + : e2str(decomp_tag); + + auto gids_f = m_io_grid->get_partitioned_dim_gids(); + auto gids_h = gids_f.get_view(); + auto min_gid = m_io_grid->get_global_min_partitioned_dim_gid(); + std::vector offsets(local_dim); + for (int idof=0; idof& fields, const bool skip_grid_checks = false); + // Due to resource acquisition (in scorpio), avoid copies + AtmosphereInput (const AtmosphereInput&) = delete; ~AtmosphereInput (); + // Due to resource acquisition (in scorpio), avoid copies + AtmosphereInput& operator= (const AtmosphereInput&) = delete; + // --- Methods --- // // Initialize the class for reading into FieldManager-owned fields. // - params: input parameters (must contain at least "Filename") @@ -123,12 +128,9 @@ class AtmosphereInput const std::map& layouts); void init_scorpio_structures (); - void register_variables(); - void set_degrees_of_freedom(); + void set_decompositions(); std::vector get_vec_of_dims (const FieldLayout& layout); - std::string get_io_decomp (const FieldLayout& layout); - std::vector get_var_dof_offsets (const FieldLayout& layout); // Internal variables ekat::ParameterList m_params; diff --git a/components/eamxx/src/share/io/scorpio_output.cpp b/components/eamxx/src/share/io/scorpio_output.cpp index 27eff12a419..b1ce7d3f326 100644 --- a/components/eamxx/src/share/io/scorpio_output.cpp +++ b/components/eamxx/src/share/io/scorpio_output.cpp @@ -613,7 +613,7 @@ run (const std::string& filename, auto view_host = m_host_views_1d.at(name); Kokkos::deep_copy (view_host,view_dev); auto func_start = std::chrono::steady_clock::now(); - grid_write_data_array(filename,name,view_host.data(),view_host.size()); + scorpio::write_var(filename,name,view_host.data()); auto func_finish = std::chrono::steady_clock::now(); auto duration_loc = std::chrono::duration_cast(func_finish - func_start); duration_write += duration_loc.count(); @@ -627,7 +627,7 @@ run (const std::string& filename, auto view_host = m_host_views_1d.at(name); Kokkos::deep_copy (view_host,view_dev); auto func_start = std::chrono::steady_clock::now(); - grid_write_data_array(filename,name,view_host.data(),view_host.size()); + scorpio::write_var(filename,name,view_host.data()); auto func_finish = std::chrono::steady_clock::now(); auto duration_loc = std::chrono::duration_cast(func_finish - func_start); duration_write += duration_loc.count(); @@ -754,31 +754,29 @@ void AtmosphereOutput::register_dimensions(const std::string& name) m_layouts.emplace(fid.name(),layout); // Now check taht all the dims of this field are already set to be registered. + const auto& tags = layout.tags(); + const auto& dims = layout.dims(); for (int i=0; iget_dim_name(layout,i); - if (tag_name=="dim") { - tag_name += std::to_string(dims[i]); - } - auto tag_loc = m_dims.find(tag_name); + std::string tag_name = m_io_grid->has_special_tag_name(tags[i]) + ? m_io_grid->get_special_tag_name(tags[i]) + : layout.names()[i]; + + // If t==CMP, and the name stored in the layout is the default ("dim"), + // we append also the extent, to allow different vector dims in the file + tag_name += tag_name=="dim" ? std::to_string(dims[i]) : ""; + auto is_partitioned = m_io_grid->get_partitioned_dim_tag()==tags[i]; - if (tag_loc == m_dims.end()) { - int tag_len = 0; - if(is_partitioned) { - // This is the dimension that is partitioned across ranks. - tag_len = m_io_grid->get_partitioned_dim_global_size(); - } else { - tag_len = layout.dim(i); - } - m_dims[tag_name] = std::make_pair(tag_len,is_partitioned); - } else { - EKAT_REQUIRE_MSG(m_dims.at(tag_name).first==dims[i] or is_partitioned, - "Error! Dimension " + tag_name + " on field " + name + " has conflicting lengths. " - "If same name applies to different dims (e.g. PhysicsGLL and PhysicsPG2 define " - "\"ncol\" at different lengths), reset tag name for one of the grids.\n"); - } + int dim_len = is_partitioned + ? m_io_grid->get_partitioned_dim_global_size() + : layout.dim(i); + auto it_bool = m_dims.emplace(tag_name,dim_len); + EKAT_REQUIRE_MSG(it_bool.second or it_bool.first->second==dim_len, + "Error! Dimension " + tag_name + " on field " + name + " has conflicting lengths.\n" + " - old length: " + std::to_string(m_dims[tag_name]) + "\n" + " - new length: " + std::to_string(dim_len) + "\n" + "If same name applies to different dims (e.g. PhysicsGLL and PhysicsPG2 define " + "\"ncol\" at different lengths), reset tag name for one of the grids.\n"); } } // register_dimensions /* ---------------------------------------------------------- */ @@ -854,8 +852,16 @@ void AtmosphereOutput::set_avg_cnt_tracking(const std::string& name, const Field const auto tags = layout.tags(); if (m_track_avg_cnt) { std::string avg_cnt_name = "avg_count" + avg_cnt_suffix; - for (int ii=0; iiget_dim_name(layout,ii); + for (int i=0; ihas_special_tag_name(t) + ? m_io_grid->get_special_tag_name(t) + : layout.names()[i]; + + // If t==CMP, and the name stored in the layout is the default ("dim"), + // we append also the extent, to allow different vector dims in the file + tag_name += tag_name=="dim" ? std::to_string(layout.dim(i)) : ""; + avg_cnt_name += "_" + tag_name; } if (std::find(m_avg_cnt_names.begin(),m_avg_cnt_names.end(),avg_cnt_name)==m_avg_cnt_names.end()) { @@ -903,7 +909,6 @@ register_variables(const std::string& filename, const std::string& fp_precision, const scorpio::FileMode mode) { - using namespace scorpio; using namespace ShortFieldTagsNames; using strvec_t = std::vector; @@ -913,39 +918,18 @@ register_variables(const std::string& filename, " - supported values: float, single, double, real\n"); // Helper lambdas - auto set_decomp_tag = [&](const FieldLayout& layout) { - std::string decomp_tag = "dt=real,grid-idx=" + std::to_string(m_io_grid->get_unique_grid_id()) + ",layout="; - - std::vector range(layout.rank()); - std::iota(range.begin(),range.end(),0); - auto tag_and_dim = [&](int i) { - return m_io_grid->get_dim_name(layout,i) + - std::to_string(layout.dim(i)); - }; - - decomp_tag += ekat::join (range, tag_and_dim,"-"); - - if (m_add_time_dim) { - decomp_tag += "-time"; - } - return decomp_tag; - }; - auto set_vec_of_dims = [&](const FieldLayout& layout) { std::vector vec_of_dims; for (int i=0; iget_dim_name(layout,i); + const auto t = layout.tag(i); + auto tag_name = m_io_grid->has_special_tag_name(t) + ? m_io_grid->get_special_tag_name(t) + : layout.names()[i]; if (tag_name=="dim") { tag_name += std::to_string(layout.dim(i)); } vec_of_dims.push_back(tag_name); // Add dimensions string to vector of dims. } - // TODO: Reverse order of dimensions to match flip between C++ -> F90 -> PIO, - // may need to delete this line when switching to fully C++/C implementation. - std::reverse(vec_of_dims.begin(),vec_of_dims.end()); - if (m_add_time_dim) { - vec_of_dims.push_back("time"); //TODO: See the above comment on time. - } return vec_of_dims; }; @@ -960,7 +944,6 @@ register_variables(const std::string& filename, // We use real here because the data type for the decomp is the one used // in the simulation and not the one used in the output file. const auto& layout = fid.get_layout(); - const auto& io_decomp_tag = set_decomp_tag(layout); auto vec_of_dims = set_vec_of_dims(layout); std::string units = fid.get_units().get_string(); @@ -971,20 +954,46 @@ register_variables(const std::string& filename, // Currently the field_manager only stores Real variables so it is not an issue, // but in the future if non-Real variables are added we will want to accomodate that. - register_variable(filename, name, longname, units, vec_of_dims, - "real",fp_precision, io_decomp_tag); + if (mode==scorpio::FileMode::Append) { + // Simply check that the var is in the file, and has the right properties + EKAT_REQUIRE_MSG (scorpio::has_var(filename,name), + "Error! Cannot append, due to variable missing from the file.\n" + " - filename : " + filename + "\n" + " - varname : " + name + "\n"); + const auto& var = scorpio::get_var(filename,name); + EKAT_REQUIRE_MSG (var.dim_names()==vec_of_dims, + "Error! Cannot append, due to variable dimensions mismatch.\n" + " - filename : " + filename + "\n" + " - varname : " + name + "\n" + " - var dims : " + ekat::join(vec_of_dims,",") + "\n" + " - var dims from file: " + ekat::join(var.dim_names(),",") + "\n"); + EKAT_REQUIRE_MSG (var.units==units, + "Error! Cannot append, due to variable units mismatch.\n" + " - filename : " + filename + "\n" + " - varname : " + name + "\n" + " - var units: " + units + "\n" + " - var units from file: " + var.units + "\n"); + EKAT_REQUIRE_MSG (var.time_dep==m_add_time_dim, + "Error! Cannot append, due to time dependency mismatch.\n" + " - filename : " + filename + "\n" + " - varname : " + name + "\n" + " - var time dep: " + (m_add_time_dim ? "yes" : "no") + "\n" + " - var time dep from file: " + (var.time_dep ? "yes" : "no") + "\n"); + } else { + scorpio::define_var (filename, name, units, vec_of_dims, + "real",fp_precision, m_add_time_dim); + + scorpio::set_attribute(filename, name, "long_name", longname); - // Add any extra attributes for this variable - if (mode != FileMode::Append ) { // Add FillValue as an attribute of each variable // FillValue is a protected metadata, do not add it if it already existed if (fp_precision=="double" or (fp_precision=="real" and std::is_same::value)) { double fill_value = m_fill_value; - set_variable_metadata(filename, name, "_FillValue",fill_value); + scorpio::set_attribute(filename, name, "_FillValue",fill_value); } else { float fill_value = m_fill_value; - set_variable_metadata(filename, name, "_FillValue",fill_value); + scorpio::set_attribute(filename, name, "_FillValue",fill_value); } // If this is has subfields, add list of its children @@ -1001,20 +1010,20 @@ register_variables(const std::string& filename, children_list.pop_back(); children_list.pop_back(); children_list += " ]"; - set_variable_metadata(filename,name,"sub_fields",children_list); + scorpio::set_attribute(filename,name,"sub_fields",children_list); } // If tracking average count variables then add the name of the tracking variable for this variable if (m_track_avg_cnt) { const auto lookup = m_field_to_avg_cnt_map.at(name); - set_variable_metadata(filename,name,"averaging_count_tracker",lookup); + scorpio::set_attribute(filename,name,"averaging_count_tracker",lookup); } // Atm procs may have set some request for metadata. using stratts_t = std::map; const auto& str_atts = field.get_header().get_extra_data("io: string attributes"); for (const auto& [att_name,att_val] : str_atts) { - set_variable_metadata(filename,name,att_name,att_val); + scorpio::set_attribute(filename,name,att_name,att_val); } } } @@ -1022,10 +1031,9 @@ register_variables(const std::string& filename, if (m_track_avg_cnt) { for (const auto& name : m_avg_cnt_names) { const auto layout = m_layouts.at(name); - auto io_decomp_tag = set_decomp_tag(layout); auto vec_of_dims = set_vec_of_dims(layout); - register_variable(filename, name, name, "unitless", vec_of_dims, - "real",fp_precision, io_decomp_tag); + scorpio::define_var(filename, name, "unitless", vec_of_dims, + "real",fp_precision, m_add_time_dim); } } } // register_variables @@ -1112,48 +1120,71 @@ AtmosphereOutput::get_var_dof_offsets(const FieldLayout& layout) return var_dof; } -/* ---------------------------------------------------------- */ -void AtmosphereOutput::set_degrees_of_freedom(const std::string& filename) + +void AtmosphereOutput::set_decompositions(const std::string& filename) { - using namespace scorpio; using namespace ShortFieldTagsNames; - // Cycle through all fields and set dof. - for (auto const& name : m_fields_names) { - auto field = get_field(name,"io"); - const auto& fid = field.get_header().get_identifier(); - auto var_dof = get_var_dof_offsets(fid.get_layout()); - set_dof(filename,name,var_dof.size(),var_dof.data()); + // First, check if any of the vars is indeed partitioned + const auto decomp_tag = m_io_grid->get_partitioned_dim_tag(); + + bool has_decomposed_layouts = false; + for (const auto& it : m_layouts) { + if (it.second.has_tag(decomp_tag)) { + has_decomposed_layouts = true; + break; + } } - // Cycle through the average count fields and set degrees of freedom - for (auto const& name : m_avg_cnt_names) { - const auto layout = m_layouts.at(name); - auto var_dof = get_var_dof_offsets(layout); - set_dof(filename,name,var_dof.size(),var_dof.data()); + if (not has_decomposed_layouts) { + // If none of the vars are decomposed on this grid, + // then there's nothing to do here + return; + } + + // Set the decomposition for the partitioned dimension + const int local_dim = m_io_grid->get_partitioned_dim_local_size(); + std::string decomp_dim = m_io_grid->has_special_tag_name(decomp_tag) + ? m_io_grid->get_special_tag_name(decomp_tag) + : e2str(decomp_tag); + auto gids_f = m_io_grid->get_partitioned_dim_gids(); + auto gids_h = gids_f.get_view(); + auto min_gid = m_io_grid->get_global_min_partitioned_dim_gid(); + std::vector offsets(local_dim); + for (int idof=0; idof get_var_dof_offsets (const FieldLayout& layout); void register_views(); Field get_field(const std::string& name, const std::string& mode) const; @@ -200,7 +200,7 @@ class AtmosphereOutput std::map m_field_to_avg_cnt_map; std::map m_field_to_avg_cnt_suffix; std::map m_layouts; - std::map> m_dims; + std::map m_dims; std::map> m_diagnostics; std::map> m_diag_depends_on_diags; std::map m_diag_computed; diff --git a/components/eamxx/src/share/io/scream_io_file_specs.hpp b/components/eamxx/src/share/io/scream_io_file_specs.hpp index c80db36e094..ec0e2f50d79 100644 --- a/components/eamxx/src/share/io/scream_io_file_specs.hpp +++ b/components/eamxx/src/share/io/scream_io_file_specs.hpp @@ -1,6 +1,7 @@ #ifndef SCREAM_IO_FILE_SPECS_HPP #define SCREAM_IO_FILE_SPECS_HPP +#include "share/io/scream_scorpio_types.hpp" #include "share/io/scream_io_utils.hpp" #include "share/util/scream_time_stamp.hpp" @@ -80,7 +81,7 @@ struct IOFileSpecs { bool is_open = false; std::string filename; - int iotype = 0; + scorpio::IOType iotype = scorpio::IOType::Invalid; // If positive, flush the output file every these many snapshots int flush_frequency = std::numeric_limits::max(); diff --git a/components/eamxx/src/share/io/scream_io_utils.cpp b/components/eamxx/src/share/io/scream_io_utils.cpp index 65e5bf030bd..4a0beb813b7 100644 --- a/components/eamxx/src/share/io/scream_io_utils.cpp +++ b/components/eamxx/src/share/io/scream_io_utils.cpp @@ -1,4 +1,6 @@ #include "share/io/scream_io_utils.hpp" + +#include "share/io/scream_scorpio_interface.hpp" #include "share/util/scream_utils.hpp" #include @@ -70,4 +72,24 @@ std::string find_filename_in_rpointer ( return filename; } +void write_timestamp (const std::string& filename, const std::string& ts_name, + const util::TimeStamp& ts, const bool write_nsteps) +{ + scorpio::set_attribute(filename,"GLOBAL",ts_name,ts.to_string()); + if (write_nsteps) { + scorpio::set_attribute(filename,"GLOBAL",ts_name+"_nsteps",ts.get_num_steps()); + } +} + +util::TimeStamp read_timestamp (const std::string& filename, + const std::string& ts_name, + const bool read_nsteps) +{ + auto ts = util::str_to_time_stamp(scorpio::get_attribute(filename,"GLOBAL",ts_name)); + if (read_nsteps and scorpio::has_attribute(filename,"GLOBAL",ts_name+"_nsteps")) { + ts.set_num_steps(scorpio::get_attribute(filename,"GLOBAL",ts_name+"_nsteps")); + } + return ts; +} + } // namespace scream diff --git a/components/eamxx/src/share/io/scream_io_utils.hpp b/components/eamxx/src/share/io/scream_io_utils.hpp index 6b50fbaba57..781b376858a 100644 --- a/components/eamxx/src/share/io/scream_io_utils.hpp +++ b/components/eamxx/src/share/io/scream_io_utils.hpp @@ -87,5 +87,12 @@ struct LongNames { }; +// Shortcut to write/read to/from YYYYMMDD/HHMMSS attributes in the NC file +void write_timestamp (const std::string& filename, const std::string& ts_name, + const util::TimeStamp& ts, const bool write_nsteps = false); +util::TimeStamp read_timestamp (const std::string& filename, + const std::string& ts_name, + const bool read_nsteps = false); + } // namespace scream #endif // SCREAM_IO_UTILS_HPP diff --git a/components/eamxx/src/share/io/scream_output_manager.cpp b/components/eamxx/src/share/io/scream_output_manager.cpp index 2458a4e95c8..35a830465bb 100644 --- a/components/eamxx/src/share/io/scream_output_manager.cpp +++ b/components/eamxx/src/share/io/scream_output_manager.cpp @@ -171,7 +171,7 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, // From restart file, get the time of last write, as well as the current size of the avg sample m_output_control.last_write_ts = read_timestamp(rhist_file,"last_write",true); m_output_control.compute_next_write_ts(); - m_output_control.nsamples_since_last_write = get_attribute(rhist_file,"num_snapshots_since_last_write"); + m_output_control.nsamples_since_last_write = get_attribute(rhist_file,"GLOBAL","num_snapshots_since_last_write"); if (m_avg_type!=OutputAvgType::Instant) { m_time_bnds.resize(2); @@ -190,24 +190,23 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, // We do NOT allow changing output specs across restart. If you do want to change // any of these, you MUST start a new output stream (e.g., setting 'Perform Restart: false') - auto old_freq = scorpio::get_attribute(rhist_file,"averaging_frequency"); + auto old_freq = scorpio::get_attribute(rhist_file,"GLOBAL","averaging_frequency"); EKAT_REQUIRE_MSG (old_freq == m_output_control.frequency, "Error! Cannot change frequency when performing history restart.\n" " - old freq: " << old_freq << "\n" " - new freq: " << m_output_control.frequency << "\n"); - auto old_freq_units = scorpio::get_attribute(rhist_file,"averaging_frequency_units"); + auto old_freq_units = scorpio::get_attribute(rhist_file,"GLOBAL","averaging_frequency_units"); EKAT_REQUIRE_MSG (old_freq_units == m_output_control.frequency_units, "Error! Cannot change frequency units when performing history restart.\n" " - old freq units: " << old_freq_units << "\n" " - new freq units: " << m_output_control.frequency_units << "\n"); - auto old_avg_type = scorpio::get_attribute(rhist_file,"averaging_type"); + auto old_avg_type = scorpio::get_attribute(rhist_file,"GLOBAL","averaging_type"); EKAT_REQUIRE_MSG (old_avg_type == e2str(m_avg_type), "Error! Cannot change avg type when performing history restart.\n" " - old avg type: " << old_avg_type + "\n" " - new avg type: " << e2str(m_avg_type) << "\n"); - - auto old_storage_type = scorpio::get_attribute(rhist_file,"file_max_storage_type"); + auto old_storage_type = scorpio::get_attribute(rhist_file,"GLOBAL","file_max_storage_type"); EKAT_REQUIRE_MSG (old_storage_type == e2str(m_output_file_specs.storage.type), "Error! Cannot change file storage type when performing history restart.\n" " - old file_max_storage_type: " << old_storage_type << "\n" @@ -216,7 +215,7 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, " Restart:\n" " force_new_file: true\n"); if (old_storage_type=="num_snapshot") { - auto old_max_snaps = scorpio::get_attribute(rhist_file,"max_snapshots_per_file"); + auto old_max_snaps = scorpio::get_attribute(rhist_file,"GLOBAL","max_snapshots_per_file"); EKAT_REQUIRE_MSG (old_max_snaps == m_output_file_specs.storage.max_snapshots_in_file, "Error! Cannot change max snapshots per file when performing history restart.\n" " - old max snaps: " << old_max_snaps << "\n" @@ -226,7 +225,7 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, " force_new_file: true\n"); } std::string fp_precision = m_params.get("Floating Point Precision"); - auto old_fp_precision = scorpio::get_attribute(rhist_file,"fp_precision"); + auto old_fp_precision = scorpio::get_attribute(rhist_file,"GLOBAL","fp_precision"); EKAT_REQUIRE_MSG (old_fp_precision == fp_precision, "Error! Cannot change floating point precision when performing history restart.\n" " - old fp precision: " << old_fp_precision << "\n" @@ -234,12 +233,12 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, // Check if the prev run wrote any output file (it may have not, if the restart was written // before the 1st output step). If there is a file, check if there's still room in it. - const auto& last_output_filename = get_attribute(rhist_file,"last_output_filename"); + const auto& last_output_filename = get_attribute(rhist_file,"GLOBAL","last_output_filename"); m_resume_output_file = last_output_filename!="" and not restart_pl.get("force_new_file",false); if (m_resume_output_file) { scorpio::register_file(last_output_filename,scorpio::Read,m_output_file_specs.iotype); int num_snaps = scorpio::get_dimlen(last_output_filename,"time"); - scorpio::eam_pio_closefile(last_output_filename); + scorpio::release_file(last_output_filename); m_output_file_specs.filename = last_output_filename; m_output_file_specs.is_open = true; @@ -249,7 +248,7 @@ setup (const ekat::Comm& io_comm, const ekat::ParameterList& params, // since those are a property of the run, not of the file. setup_file(m_output_file_specs,m_output_control); } - scorpio::eam_pio_closefile(rhist_file); + scorpio::release_file(rhist_file); } } @@ -342,7 +341,7 @@ void OutputManager::run(const util::TimeStamp& timestamp) snapshot_start += m_time_bnds[0]; } if (not filespecs.storage.snapshot_fits(snapshot_start)) { - eam_pio_closefile(filespecs.filename); + release_file(filespecs.filename); filespecs.close(); } @@ -388,14 +387,14 @@ void OutputManager::run(const util::TimeStamp& timestamp) setup_output_file(m_output_control,m_output_file_specs); // Update time (must be done _before_ writing fields) - pio_update_time(m_output_file_specs.filename,timestamp.days_from(m_case_t0)); + update_time(m_output_file_specs.filename,timestamp.days_from(m_case_t0)); } if (is_checkpoint_step) { setup_output_file(m_checkpoint_control,m_checkpoint_file_specs); if (is_full_checkpoint_step) { // Update time (must be done _before_ writing fields) - pio_update_time(m_checkpoint_file_specs.filename,timestamp.days_from(m_case_t0)); + update_time(m_checkpoint_file_specs.filename,timestamp.days_from(m_case_t0)); } } stop_timer(timer_root+"::get_new_file"); @@ -436,44 +435,63 @@ void OutputManager::run(const util::TimeStamp& timestamp) if (m_is_model_restart_output) { // Only write nsteps on model restart - set_attribute(filespecs.filename,"nsteps",timestamp.get_num_steps()); + set_attribute(filespecs.filename,"GLOBAL","nsteps",timestamp.get_num_steps()); } else { if (filespecs.ftype==FileType::HistoryRestart) { // Update the date of last write and sample size - scorpio::write_timestamp (filespecs.filename,"last_write",m_output_control.last_write_ts,true); - scorpio::set_attribute (filespecs.filename,"last_output_filename",m_output_file_specs.filename); - scorpio::set_attribute (filespecs.filename,"num_snapshots_since_last_write",m_output_control.nsamples_since_last_write); + write_timestamp (filespecs.filename,"last_write",m_output_control.last_write_ts,true); + scorpio::set_attribute (filespecs.filename,"GLOBAL","last_output_filename",m_output_file_specs.filename); + scorpio::set_attribute (filespecs.filename,"GLOBAL","num_snapshots_since_last_write",m_output_control.nsamples_since_last_write); } // Write these in both output and rhist file. The former, b/c we need these info when we postprocess // output, and the latter b/c we want to make sure these params don't change across restarts - set_attribute(filespecs.filename,"averaging_type",e2str(m_avg_type)); - set_attribute(filespecs.filename,"averaging_frequency_units",m_output_control.frequency_units); - set_attribute(filespecs.filename,"averaging_frequency",m_output_control.frequency); - set_attribute(filespecs.filename,"file_max_storage_type",e2str(m_output_file_specs.storage.type)); + set_attribute(filespecs.filename,"GLOBAL","averaging_type",e2str(m_avg_type)); + set_attribute(filespecs.filename,"GLOBAL","averaging_frequency_units",m_output_control.frequency_units); + set_attribute(filespecs.filename,"GLOBAL","averaging_frequency",m_output_control.frequency); + set_attribute(filespecs.filename,"GLOBAL","file_max_storage_type",e2str(m_output_file_specs.storage.type)); if (m_output_file_specs.storage.type==NumSnaps) { - set_attribute(filespecs.filename,"max_snapshots_per_file",m_output_file_specs.storage.max_snapshots_in_file); + set_attribute(filespecs.filename,"GLOBAL","max_snapshots_per_file",m_output_file_specs.storage.max_snapshots_in_file); } const auto& fp_precision = m_params.get("Floating Point Precision"); - set_attribute(filespecs.filename,"fp_precision",fp_precision); + set_attribute(filespecs.filename,"GLOBAL","fp_precision",fp_precision); } // Write all stored globals for (const auto& it : m_globals) { const auto& name = it.first; const auto& any = it.second; - set_any_attribute(filespecs.filename,name,any); + if (any.isType()) { + set_attribute(filespecs.filename,"GLOBAL",name,ekat::any_cast(any)); + } else if (any.isType()) { + set_attribute(filespecs.filename,"GLOBAL",name,ekat::any_cast(any)); + } else if (any.isType()) { + set_attribute(filespecs.filename,"GLOBAL",name,ekat::any_cast(any)); + } else if (any.isType()) { + set_attribute(filespecs.filename,"GLOBAL",name,ekat::any_cast(any)); + } else if (any.isType()) { + set_attribute(filespecs.filename,"GLOBAL",name,ekat::any_cast(any)); + } else { + EKAT_ERROR_MSG ( + "Error! Invalid concrete type for IO global.\n" + " - global name: " + it.first + "\n" + " - type id : " + any.content().type().name() + "\n"); + } } // We're adding one snapshot to the file filespecs.storage.update_storage(timestamp); - if (m_time_bnds.size()>0) { - scorpio::grid_write_data_array(filespecs.filename, "time_bnds", m_time_bnds.data(), 2); + // NOTE: for checkpoint files, unless we write restart data, we did not update time, + // which means we cannot write any variable (the check var.num_records==time.length + // would fail) + if (m_time_bnds.size()>0 and + (filespecs.ftype!=FileType::HistoryRestart or is_full_checkpoint_step)) { + scorpio::write_var(filespecs.filename, "time_bnds", m_time_bnds.data()); } // Check if we need to flush the output file if (filespecs.file_needs_flush()) { - eam_flush_file (filespecs.filename); + flush_file (filespecs.filename); } }; @@ -500,10 +518,10 @@ void OutputManager::finalize() { // Close any output file still open if (m_output_file_specs.is_open) { - scorpio::eam_pio_closefile (m_output_file_specs.filename); + scorpio::release_file (m_output_file_specs.filename); } if (m_checkpoint_file_specs.is_open) { - scorpio::eam_pio_closefile (m_checkpoint_file_specs.filename); + scorpio::release_file (m_checkpoint_file_specs.filename); } // Swapping with an empty mgr is the easiest way to cleanup. @@ -691,8 +709,6 @@ void OutputManager:: setup_file ( IOFileSpecs& filespecs, const IOControl& control) { - using namespace scorpio; - const bool is_checkpoint_step = &control==&m_checkpoint_control; std::string fp_precision = is_checkpoint_step @@ -701,59 +717,47 @@ setup_file ( IOFileSpecs& filespecs, const auto& filename = filespecs.filename; // Register new netCDF file for output. Check if we need to append to an existing file - auto mode = m_resume_output_file ? Append : Write; - register_file(filename,mode,filespecs.iotype); + auto mode = m_resume_output_file ? scorpio::Append : scorpio::Write; + scorpio::register_file(filename,mode,filespecs.iotype); if (m_resume_output_file) { - eam_pio_redef(filename); - } + scorpio::redef(filename); + } else { + // Register time (and possibly time_bnds) var(s) + auto time_units="days since " + m_case_t0.get_date_string() + " " + m_case_t0.get_time_string(); + scorpio::define_time(filename,time_units,"time"); - // Note: length=0 is how scorpio recognizes that this is an 'unlimited' dimension, which - // allows to write as many timesnaps as we desire. - register_dimension(filename,"time","time",0,false); + scorpio::define_var(filename,"time",time_units,{}, "double", "double",true); + if (use_leap_year()) { + scorpio::set_attribute (filename,"time","calendar","gregorian"); + } else { + scorpio::set_attribute (filename,"time","calendar","noleap"); + } - // Register time (and possibly time_bnds) var(s) - auto time_units="days since " + m_case_t0.get_date_string() + " " + m_case_t0.get_time_string(); - register_variable(filename,"time","time",time_units,{"time"}, "double", "double","time"); - if (use_leap_year()) { - set_variable_metadata (filename,"time","calendar","gregorian"); - } else { - set_variable_metadata (filename,"time","calendar","noleap"); - } - if (m_avg_type!=OutputAvgType::Instant) { - // First, ensure a 'dim2' dimension with len=2 is registered. - register_dimension(filename,"dim2","dim2",2,false); - register_variable(filename,"time_bnds","time_bnds",time_units,{"dim2","time"},"double","double","time-dim2"); - - // Make it clear how the time_bnds should be interpreted - set_variable_metadata(filename,"time_bnds","note","right endpoint accumulation"); - - // I'm not sure what's the point of this, but CF conventions seem to require it - set_variable_metadata (filename,"time","bounds","time_bnds"); - } + if (m_avg_type!=OutputAvgType::Instant) { + // First, ensure a 'dim2' dimension with len=2 is registered. + scorpio::define_dim(filename,"dim2",2); + scorpio::define_var(filename,"time_bnds",time_units,{"dim2"},"double","double",true); + + // Make it clear how the time_bnds should be interpreted + scorpio::set_attribute (filename,"time_bnds","note","right endpoint accumulation"); + + // I'm not sure what's the point of this, but CF conventions seem to require it + scorpio::set_attribute (filename,"time","bounds","time_bnds"); + } - if (not m_resume_output_file) { - // Finish the definition phase for this file. write_timestamp(filename,"case_t0",m_case_t0); write_timestamp(filename,"run_t0",m_run_t0); - set_attribute(filename,"averaging_type",e2str(m_avg_type)); - set_attribute(filename,"averaging_frequency_units",m_output_control.frequency_units); - set_attribute(filename,"averaging_frequency",m_output_control.frequency); - set_attribute(filespecs.filename,"file_max_storage_type",e2str(m_output_file_specs.storage.type)); + scorpio::set_attribute(filename,"GLOBAL","averaging_type",e2str(m_avg_type)); + scorpio::set_attribute(filename,"GLOBAL","averaging_frequency_units",m_output_control.frequency_units); + scorpio::set_attribute(filename,"GLOBAL","averaging_frequency",m_output_control.frequency); + scorpio::set_attribute(filespecs.filename,"GLOBAL","file_max_storage_type",e2str(m_output_file_specs.storage.type)); if (m_output_file_specs.storage.type==NumSnaps) { - set_attribute(filename,"max_snapshots_per_file",m_output_file_specs.storage.max_snapshots_in_file); + scorpio::set_attribute(filename,"GLOBAL","max_snapshots_per_file",m_output_file_specs.storage.max_snapshots_in_file); } - set_attribute(filename,"fp_precision",fp_precision); + scorpio::set_attribute(filename,"GLOBAL","fp_precision",fp_precision); set_file_header(filespecs); } - // Set degree of freedom for "time" and "time_bnds" - scorpio::offset_t time_dof[1] = {0}; - set_dof(filename,"time",1,time_dof); - if (m_avg_type!=OutputAvgType::Instant) { - scorpio::offset_t time_bnds_dofs[2] = {0,1}; - set_dof(filename,"time_bnds",2,time_bnds_dofs); - } - // Make all output streams register their dims/vars for (auto& it : m_output_streams) { it->setup_output_file(filename,fp_precision,mode); @@ -767,10 +771,7 @@ setup_file ( IOFileSpecs& filespecs, } } - // When resuming a file, PIO opens it in data mode. - // NOTE: all the above register_dimension/register_variable are already checking that - // the dims/vars are already in the file (we don't allow adding dims/vars) - eam_pio_enddef (filename); + scorpio::enddef (filename); if (m_save_grid_data and not filespecs.is_restart_file() and not m_resume_output_file) { // Immediately run the geo data streams @@ -786,11 +787,6 @@ setup_file ( IOFileSpecs& filespecs, /*===============================================================================================*/ void OutputManager::set_file_header(const IOFileSpecs& file_specs) { - using namespace scorpio; - - // TODO: All attributes marked TODO below need to be set. Hopefully by a universal value that reflects - // what the attribute is. For example, git-hash should be the git-hash associated with this version of - // the code at build time for this executable. auto& p = m_params.sublist("provenance"); auto now = std::chrono::system_clock::now(); std::time_t time = std::chrono::system_clock::to_time_t(now); @@ -801,20 +797,24 @@ void OutputManager::set_file_header(const IOFileSpecs& file_specs) const auto& filename = file_specs.filename; - set_attribute(filename,"case",p.get("caseid","NONE")); - set_attribute(filename,"source","E3SM Atmosphere Model (EAMxx)"); - set_attribute(filename,"eamxx_version",EAMXX_VERSION); - set_attribute(filename,"git_version",p.get("git_version",EAMXX_GIT_VERSION)); - set_attribute(filename,"hostname",p.get("hostname","UNKNOWN")); - set_attribute(filename,"username",p.get("username","UNKNOWN")); - set_attribute(filename,"atm_initial_conditions_file",p.get("initial_conditions_file","NONE")); - set_attribute(filename,"topography_file",p.get("topography_file","NONE")); - set_attribute(filename,"contact","e3sm-data-support@llnl.gov"); - set_attribute(filename,"institution_id","E3SM-Projet"); - set_attribute(filename,"realm","atmos"); - set_attribute(filename,"history",ts_str); - set_attribute(filename,"Conventions","CF-1.8"); - set_attribute(filename,"product",e2str(file_specs.ftype)); + auto set_str_att = [&](const std::string& name, const std::string& val) { + scorpio::set_attribute(filename,"GLOBAL",name,val); + }; + + set_str_att("case",p.get("caseid","NONE")); + set_str_att("source","E3SM Atmosphere Model (EAMxx)"); + set_str_att("eamxx_version",EAMXX_VERSION); + set_str_att("git_version",p.get("git_version",EAMXX_GIT_VERSION)); + set_str_att("hostname",p.get("hostname","UNKNOWN")); + set_str_att("username",p.get("username","UNKNOWN")); + set_str_att("atm_initial_conditions_file",p.get("initial_conditions_file","NONE")); + set_str_att("topography_file",p.get("topography_file","NONE")); + set_str_att("contact","e3sm-data-support@llnl.gov"); + set_str_att("institution_id","E3SM-Project"); + set_str_att("realm","atmos"); + set_str_att("history",ts_str); + set_str_att("Conventions","CF-1.8"); + set_str_att("product",e2str(file_specs.ftype)); } /*===============================================================================================*/ void OutputManager:: diff --git a/components/eamxx/src/share/io/scream_scorpio_interface.F90 b/components/eamxx/src/share/io/scream_scorpio_interface.F90 deleted file mode 100644 index bfd9ad68f2d..00000000000 --- a/components/eamxx/src/share/io/scream_scorpio_interface.F90 +++ /dev/null @@ -1,1889 +0,0 @@ -module scream_scorpio_interface - -!==============================================================================! -! This module handles the Fortran interface to the PIO library of input/output -! subroutines. The essential set of steps to enable and use PIO for creating -! output are as follows: -! OUTPUT -! Initialization: -! 1) Gather the pio_subsystem and pio_iotype information for the EAM component -! as assigned by the component coupler. -! This is accomplished during eam_init_pio_1 by calling -! 'eam_init_pio_subsystem' -! 2) For each output file "create" a file in PIO and record the unique file -! descriptor. -! This is accomplished during the eam_init_pio_1 by calling -! 'eam_pio_createfile' -! 3) For each output file define the "header" information, which is essentially -! a set of metadata strings that describe the output file. -! This is accomplished during eam_init_pio_1 by calling 'eam_pio_createHeader' -! 4) Define all of the dimensions that variables in this file will be defined -! on. Examples would time, lat, lon, vertical coordinate, # of consituents, -! etc. -! This is accomplished during 'register_dimension' by which calls 'PIO_def_dim' -! 5) Define all of the variables that will be written to this file. This -! includes any dimension that should also be defined as a variable, such as lat, -! lon, time. -! This is accomplished during 'register_variable' which calls to 'PIO_def_var' -! 6) Determine the unique PIO identifiers for each domain decomposition. This -! is essentially a decomposition of what will be written to the file, multiple -! variables can have the same decomposition. Every arrangement of dimensions -! requires a pio decomposition. -! This is accomplished during 'register_variable' by calling -! 'PIO_initdecomp' -! 7) Close the PIO file definition step. In other words, tell PIO that all of -! the dimensions, variables and decompositions associated with this output have -! been defined and no new ones will be added. -! This is accomplished during eam_pio_enddef by calling 'PIO_enddef' on all -! defined files. -! -! Writing Output: -! -! Finalization: -!==============================================================================! - - !------------ - use pio_types, only: iosystem_desc_t, file_desc_t, var_desc_t, io_desc_t, & - pio_noerr, pio_global, & - PIO_int, PIO_real, PIO_double, PIO_float=>PIO_real,& - pio_iotype_netcdf, pio_iotype_pnetcdf, pio_iotype_adios - use pio_kinds, only: PIO_OFFSET_KIND - - use mpi, only: mpi_abort, mpi_comm_size, mpi_comm_rank - - use iso_c_binding, only: c_float, c_double, c_int - implicit none - save - - public :: & - lookup_pio_atm_file, & ! Checks if a pio file is present - eam_pio_closefile, & ! Close a specfic pio file. - eam_pio_flush_file, & ! Flushes I/O buffers to file - eam_pio_enddef, & ! Ends define mode phase, enters data mode phase - eam_pio_redef, & ! Pause data mode phase, re-enter define mode phase - eam_init_pio_subsystem, & ! Gather pio specific data from the component coupler - is_eam_pio_subsystem_inited, & ! Query whether the pio subsystem is inited already - eam_pio_finalize, & ! Run any final PIO commands - register_file, & ! Creates/opens a pio input/output file - register_variable, & ! Register a variable with a particular pio output file - set_variable_metadata_char, & ! Sets a variable metadata (char data) - set_variable_metadata_float, & ! Sets a variable metadata (float data) - set_variable_metadata_double,& ! Sets a variable metadata (double data) - get_variable_metadata_char, & ! Gets a variable metadata (char data) - get_variable_metadata_float, & ! Gets a variable metadata (float data) - get_variable_metadata_double,& ! Gets a variable metadata (double data) - register_dimension, & ! Register a dimension with a particular pio output file - set_decomp, & ! Set the pio decomposition for all variables in file. - set_dof, & ! Set the pio dof decomposition for specific variable in file. - grid_write_data_array, & ! Write gridded data to a pio managed netCDF file - grid_read_data_array, & ! Read gridded data from a pio managed netCDF file - eam_update_time, & ! Update the timestamp (i.e. time variable) for a given pio netCDF file - read_time_at_index ! Returns the time stamp for a specific time index - - private :: errorHandle, get_coord, is_read, is_write, is_append, scream_iotype_to_pio_iotype - - ! Universal PIO variables for the module - integer :: atm_mpicom - integer :: pio_iotype - type(iosystem_desc_t), pointer, public :: pio_subsystem - integer :: pio_rearranger - integer :: pio_mode - integer :: time_dimid = -1 - - ! TYPES to handle history coordinates and files - integer,parameter :: max_hcoordname_len = 16 - integer,parameter :: max_chars = 256 - integer,parameter :: max_hvarname_len = 64 - integer,parameter :: max_hvar_dimlen = 5 - integer,parameter :: file_purpose_not_set = 0 ! Not set (to catch uninited stuff errors) - integer,parameter :: file_purpose_in = 1 ! Read - integer,parameter :: file_purpose_app = 2 ! Write (append if file exists) - integer,parameter :: file_purpose_out = 4 ! Write (replace if file exists) - - type, public :: hist_coord_t - character(len=max_hcoordname_len) :: name = '' ! coordinate name - integer :: dimid ! Unique PIO Id for this dimension - character(len=max_chars) :: long_name = '' ! 'long_name' attribute - character(len=max_chars) :: units = '' ! 'units' attribute - logical :: is_partitioned ! whether the dimension is partitioned across ranks - logical :: is_time_dim ! whether the dimension is the time (unlimited) dim - end type hist_coord_t - - type, public :: hist_var_t - character(len=max_hvarname_len) :: name ! coordinate name - character(len=max_chars) :: long_name ! 'long_name' attribute - character(len=max_chars) :: pio_decomp_tag ! PIO decomposition label used by this variable. - character(len=max_chars) :: units ! 'units' attribute - type(var_desc_t) :: piovar ! netCDF variable ID - integer :: dtype ! data type used to pass data to read/write routines - integer :: nc_dtype ! data type used in the netcdf files - integer :: numdims ! Number of dimensions in out field - type(io_desc_t), pointer :: iodesc => NULL() ! PIO decomp associated with this variable - type(iodesc_list_t), pointer :: iodesc_list ! PIO decomp list with metadata about PIO decomp - integer(kind=pio_offset_kind), allocatable :: compdof(:) ! Global locations in output array for this process - integer, allocatable :: dimid(:) ! array of PIO dimension id's for this variable - integer, allocatable :: dimlen(:) ! array of PIO dimension lengths for this variable - logical :: has_t_dim ! true, if variable has a time dimension - logical :: is_set = .false. ! Safety measure to ensure a deallocated hist_var_t is never used - logical :: is_partitioned ! Whether at least one of the dims is partitioned - end type hist_var_t - - ! The iodesc_list allows us to cache existing PIO decompositions - ! The tag needs the dim lengths, the dtype and map id (+ optional permutation) - ! Define a recursive structure because we do not know ahead of time how many - ! decompositions will be require - type iodesc_list_t - character(max_chars) :: tag ! Unique tag associated with this decomposition - type(io_desc_t), pointer :: iodesc => NULL() ! PIO - decomposition - type(iodesc_list_t), pointer :: next => NULL() ! Needed for recursive definition, the next list - type(iodesc_list_t), pointer :: prev => NULL() ! Needed for recursive definition, the list that points to this one - logical :: iodesc_set = .false. - integer :: num_customers = 0 ! Track the number of currently active variables that use this pio decomposition - integer :: location = 0 ! where am in the recursive list - end type iodesc_list_t - - ! Define the first iodesc_list_t - type(iodesc_list_t), pointer :: iodesc_list_top -!---------------------------------------------------------------------- - type hist_coord_list_t - type(hist_coord_t), pointer :: coord => NULL() ! Pointer to a history dimension structure - type(hist_coord_list_t), pointer :: next => NULL() ! Needed for recursive definition - end type hist_coord_list_t -!---------------------------------------------------------------------- - type hist_var_list_t - type(hist_var_t), pointer :: var => NULL() ! Pointer to a history variable structure - type(hist_var_list_t), pointer :: next => NULL() ! Needed for recursive definition - end type hist_var_list_t -!---------------------------------------------------------------------- - type pio_file_list_t - type(pio_atm_file_t), pointer :: pio_file => NULL() ! Pointer to an atm. pio file - type(pio_file_list_t), pointer :: next => NULL() ! Needed for recursive definition - type(pio_file_list_t), pointer :: prev => NULL() ! A doubly-linked list is easier to handle - end type pio_file_list_t - ! Define the first pio_file_list - type(pio_file_list_t), pointer :: pio_file_list_front - type(pio_file_list_t), pointer :: pio_file_list_back - -!---------------------------------------------------------------------- - type, public :: pio_atm_file_t - character(len=max_chars) :: filename = "" - integer :: purpose = file_purpose_not_set ! Input or Output file - type(file_desc_t) :: pioFileDesc ! Contains data identifying the file. - type(hist_coord_list_t) :: coord_list_top ! Recursive list of variables - type(hist_var_list_t) :: var_list_top ! Recursive list of variables - integer :: numRecs ! Number of history records on file - logical :: is_enddef = .false. ! Whether definition phase is open - integer :: num_customers ! The number of customer that requested to open the file. - end type pio_atm_file_t - -!---------------------------------------------------------------------- - interface grid_read_data_array - module procedure grid_read_darray_double - module procedure grid_read_darray_float - module procedure grid_read_darray_int - end interface grid_read_data_array -!---------------------------------------------------------------------- - interface grid_write_data_array - module procedure grid_write_darray_float - module procedure grid_write_darray_double - module procedure grid_write_darray_int - end interface -!---------------------------------------------------------------------- - -contains -!=====================================================================! - ! Register a PIO file to be used for input/output operations. - ! If file is already open, ensures file_purpose matches the current one - subroutine register_file(filename,file_purpose,iotype) - - character(len=*), intent(in) :: filename - integer, intent(in) :: file_purpose - integer, intent(in) :: iotype - - type(pio_atm_file_t), pointer :: pio_file - - if (.not.associated(pio_subsystem)) then - call errorHandle("PIO ERROR: local pio_subsystem pointer has not been established yet.",-999) - endif - - call get_pio_atm_file(filename,pio_file,file_purpose,iotype) - end subroutine register_file -!=====================================================================! - ! Mandatory call to finish the variable and dimension definition phase - ! of a new PIO file. Once this routine is called it is not possible - ! to add new dimensions or variables to the file. - subroutine eam_pio_enddef(filename) - use pio, only: PIO_enddef - - character(len=*), intent(in) :: filename - - type(pio_atm_file_t), pointer :: current_atm_file - integer :: ierr - logical :: found - - call lookup_pio_atm_file(filename,current_atm_file,found) - if (.not.found) then - call errorHandle("PIO ERROR: error running enddef on file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! It could happen that we are running a test, with an input file opening the - ! same file that an output stream just wrote. In this case, the def phase ended - ! during the output setup. - if (.not. current_atm_file%is_enddef) then - ! Gather the pio decomposition for all variables in this file, and assign them pointers. - call set_decomp(trim(filename)) - ! Officially close the definition step for this file. - ierr = PIO_enddef(current_atm_file%pioFileDesc) - call errorHandle("PIO ERROR: issue arose with PIO_enddef for file"//trim(current_atm_file%filename),ierr) - current_atm_file%is_enddef = .true. - endif - - end subroutine eam_pio_enddef -!=====================================================================! - ! Mandatory call to finish the variable and dimension definition phase - ! of a new PIO file. Once this routine is called it is not possible - ! to add new dimensions or variables to the file. - subroutine eam_pio_redef(filename) - use pio, only: pio_redef - - character(len=*), intent(in) :: filename - - type(pio_atm_file_t), pointer :: current_atm_file - integer :: ierr - logical :: found - - call lookup_pio_atm_file(filename,current_atm_file,found) - if (.not.found) then - call errorHandle("PIO ERROR: error running redef on file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! It could happen that we are running a test, with an input file opening the - ! same file that an output stream just wrote. In this case, the def phase ended - ! during the output setup. - if (current_atm_file%is_enddef) then - ! Re-open define phase - ierr = PIO_redef(current_atm_file%pioFileDesc) - call errorHandle("PIO ERROR: issue arose with PIO_redef for file"//trim(current_atm_file%filename),ierr) - current_atm_file%is_enddef = .false. - endif - - end subroutine eam_pio_redef -!=====================================================================! - ! Register a dimension with a specific pio output file. Mandatory inputs - ! include: - ! filename: Name of file to add the dimension to. - ! shortname: Short name descriptor for this dimension. This will be - ! name to find the dimension in the netCDF file. - ! longname: A longer character string with a more descriptive name of - ! the dimension. - ! length: The dimension length (must be >=0). Choosing 0 marks the - ! dimensions as having "unlimited" length which is used for - ! dimensions such as time. - ! NOTE: if the file is in Read or Append mode, we are supposed to have already checked - ! that the specs match the ones in the file during the C++ wrapper functions - subroutine register_dimension(filename,shortname,longname,length,is_partitioned) - use pio_types, only: pio_unlimited - use pio, only: PIO_def_dim - - character(len=*), intent(in) :: filename ! Name of file to register the dimension on. - character(len=*), intent(in) :: shortname,longname ! Short- and long- names for this dimension, short: brief identifier and name for netCDF output, long: longer descriptor sentence to be included as meta-data in file. - integer, intent(in) :: length ! Length of the dimension, 0: unlimited (like time), >0 actual length of dimension - logical, intent(in) :: is_partitioned ! whether this dimension is partitioned across ranks - - type(pio_atm_file_t), pointer :: pio_atm_file - type(hist_coord_t), pointer :: hist_coord - type(hist_coord_list_t), pointer :: curr, prev - logical :: found, dim_found - integer :: ierr - - dim_found = .false. - - ! Make sure the dimension length is reasonable - if (length<0) call errorHandle("PIO Error: dimension "//trim(shortname)//", can't have a negative dimension length",-999) - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: error registering dimension "//trim(shortname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! Get a new dimension pointer in coord_list - curr => pio_atm_file%coord_list_top - do while (associated(curr)) - if (associated(curr%coord)) then - if(trim(curr%coord%name)==trim(shortname)) then - dim_found = .true. - exit - endif - end if - prev => curr - curr => prev%next - end do - - ! If the dim was not found, create it - if (.not. dim_found) then - allocate(prev%next) - curr => prev%next - allocate(curr%coord) - hist_coord => curr%coord - - ! Register this dimension - hist_coord%name = trim(shortname) - hist_coord%long_name = trim(longname) - hist_coord%is_partitioned = is_partitioned - hist_coord%is_time_dim = length .eq. 0 - - if (is_write(pio_atm_file%purpose)) then - if (length.eq.0) then - ierr = PIO_def_dim(pio_atm_file%pioFileDesc, trim(shortname), pio_unlimited , hist_coord%dimid) - time_dimid = hist_coord%dimid - else - ierr = PIO_def_dim(pio_atm_file%pioFileDesc, trim(shortname), length , hist_coord%dimid) - end if - call errorHandle("PIO ERROR: could not define dimension "//trim(shortname)//" on file: "//trim(filename),ierr) - endif - endif - end subroutine register_dimension -!=====================================================================! - ! Register a variable with a specific pio input/output file. Mandatory inputs - ! include: - ! pio_atm_filename: The name of the netCDF file this variable will be - ! registered with. - ! shortname: A shortname descriptor (tag) for this variable. This will be - ! used to label the variable in the netCDF file as well. - ! longname: A longer character string describing the variable. - ! numdims: The number of dimensions associated with this variable, - ! including time (if applicable). - ! var_dimensions: An array of character strings with the dimension shortnames - ! for each dimension used by this variable. Should have - ! 'numdims' entries. - ! dtype: The data type for this variable using the proper netCDF - ! integer tag. - ! pio_decomp_tag: A string that describes this particular dimension - ! arrangement which will be used to create a unique PIO - ! decomposition for reading this variable. It is ok to reuse - ! the pio_decomp_tag for variables that have the same - ! dimensionality. See get_decomp for more details. - ! NOTE: if the file is in Read or Append mode, we are supposed to have already checked - ! that the specs match the ones in the file during the C++ wrapper functions - subroutine register_variable(filename,shortname,longname,units, & - numdims,var_dimensions, & - dtype,nc_dtype,pio_decomp_tag) - use pio, only: PIO_def_var, PIO_inq_dimid, PIO_inq_dimlen, PIO_inq_varid, PIO_put_att - - character(len=256), intent(in) :: filename ! Name of the file to register this variable with - character(len=256), intent(in) :: shortname,longname ! short and long names for the variable. Short: variable name in file, Long: more descriptive name - character(len=256), intent(in) :: units ! units for variable - integer, intent(in) :: numdims ! Number of dimensions for this variable, including time dimension - character(len=256), intent(in) :: var_dimensions(numdims) ! String array with shortname descriptors for each dimension of variable. - character(len=256), intent(in) :: pio_decomp_tag ! Unique tag for this variables decomposition type, to be used to determine if the io-decomp already exists. - integer, intent(in) :: dtype ! datatype for arrays that will be passed to read/write routines - integer, intent(in) :: nc_dtype ! datatype for this variable in nc files (unused if file mode is Read) - - ! Local variables - type(pio_atm_file_t),pointer :: pio_atm_file - type(hist_var_t), pointer :: hist_var - integer :: dim_ii - logical :: found,var_found - integer :: ierr - type(hist_coord_t), pointer :: hist_coord - - type(hist_var_list_t), pointer :: curr, prev - - var_found = .false. - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: error registering variable "//trim(shortname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! Get a new variable pointer in var_list - if (len_trim(shortname)>max_hvarname_len) call errorHandle("PIO Error: variable shortname "//trim(shortname)//" is too long, consider increasing max_hvarname_len or changing the variable shortname",-999) - curr => pio_atm_file%var_list_top - do while ( associated(curr) ) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(shortname) .and. curr%var%is_set) then - exit - end if - end if - prev => curr - curr => prev%next - end do - - allocate(prev%next) - curr => prev%next - allocate(curr%var) - hist_var => curr%var - ! Populate meta-data associated with this variable - hist_var%name = trim(shortname) - hist_var%long_name = trim(longname) - - hist_var%units = trim(units) - hist_var%numdims = numdims - hist_var%dtype = dtype - hist_var%nc_dtype = nc_dtype - hist_var%pio_decomp_tag = trim(pio_decomp_tag) - ! Determine the dimension id's saved in the netCDF file and associated with - ! this variable, check if variable has a time dimension - hist_var%has_t_dim = .false. - hist_var%is_partitioned = .false. - allocate(hist_var%dimid(numdims),hist_var%dimlen(numdims)) - do dim_ii = 1,numdims - ierr = pio_inq_dimid(pio_atm_file%pioFileDesc,trim(var_dimensions(dim_ii)),hist_var%dimid(dim_ii)) - call errorHandle("EAM_PIO ERROR: Unable to find dimension id for "//trim(var_dimensions(dim_ii)),ierr) - ierr = pio_inq_dimlen(pio_atm_file%pioFileDesc,hist_var%dimid(dim_ii),hist_var%dimlen(dim_ii)) - call errorHandle("EAM_PIO ERROR: Unable to determine length for dimension "//trim(var_dimensions(dim_ii)),ierr) - - call get_coord (filename,var_dimensions(dim_ii),hist_coord) - if (hist_coord%is_partitioned) then - hist_var%is_partitioned = .true. - endif - if (hist_coord%is_time_dim) then - hist_var%has_t_dim = .true. - endif - end do - - if (is_write(pio_atm_file%purpose)) then - ierr = PIO_def_var(pio_atm_file%pioFileDesc, trim(shortname), hist_var%nc_dtype, hist_var%dimid(:numdims), hist_var%piovar) - call errorHandle("PIO ERROR: could not define variable "//trim(shortname)//" in file "//trim(filename),ierr) - ierr=PIO_put_att(pio_atm_file%pioFileDesc, hist_var%piovar, 'units', hist_var%units ) - call errorHandle("PIO ERROR: could not set attribute 'units' for variable "//trim(shortname)//" in file "//trim(filename),ierr) - ierr=PIO_put_att(pio_atm_file%pioFileDesc, hist_var%piovar, 'long_name', hist_var%long_name ) - call errorHandle("PIO ERROR: could not set attribute 'long_name' for variable "//trim(shortname)//" in file "//trim(filename),ierr) - else - ierr = PIO_inq_varid(pio_atm_file%pioFileDesc,trim(shortname),hist_var%piovar) - call errorHandle("PIO ERROR: could not retrieve id for variable "//trim(shortname)//" from file "//trim(filename),ierr) - endif - - ! Set that new variable has been created - hist_var%is_set = .true. - - end subroutine register_variable -!=====================================================================! - subroutine set_variable_metadata_float(filename, varname, metaname, metaval) - use pio, only: PIO_put_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - real(kind=c_float), intent(in) :: metaval - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n Variable not found.",-999) - endif - - ierr = PIO_put_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error setting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - - end subroutine set_variable_metadata_float -!=====================================================================! - subroutine set_variable_metadata_double(filename, varname, metaname, metaval) - use pio, only: PIO_put_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - real(kind=c_double), intent(in) :: metaval - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n Variable not found.",-999) - endif - - ierr = PIO_put_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error setting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - - end subroutine set_variable_metadata_double -!=====================================================================! - function get_variable_metadata_float(filename, varname, metaname) result(metaval) - use pio, only: PIO_get_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - real(kind=c_float) :: metaval - - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ! TODO: Maybe we shouldn't throw an error when the metadata isn't there, maybe we provide an output like ierr as an integer? - ! That way we can query for a metadata and on the EAMxx side decide what to do if the metadata is missing. - ierr = PIO_get_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error getting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - - end function get_variable_metadata_float -!=====================================================================! - function get_variable_metadata_double(filename, varname, metaname) result(metaval) - use pio, only: PIO_get_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - real(kind=c_double) :: metaval - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ierr = PIO_get_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error getting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - - end function get_variable_metadata_double -!=====================================================================! - subroutine set_variable_metadata_char(filename, varname, metaname, metaval) - use pio, only: PIO_put_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - character(len=256), intent(in) :: metaval - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: error setting metadata for variable "//trim(varname)//" in file "//trim(filename)//".\n Variable not found.",-999) - endif - - ierr = PIO_put_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error setting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - - end subroutine set_variable_metadata_char -!=====================================================================! - function get_variable_metadata_char(filename, varname, metaname) result(metaval) - use pio, only: PIO_get_att - - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: varname - character(len=256), intent(in) :: metaname - character(len=256) :: metaval - - ! Local variables - type(pio_atm_file_t),pointer :: pio_file - type(hist_var_t), pointer :: var - integer :: ierr - logical :: found - - type(hist_var_list_t), pointer :: curr - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ! Find the variable in the file - curr => pio_file%var_list_top - - found = .false. - do while (associated(curr)) - if (associated(curr%var)) then - if (trim(curr%var%name)==trim(varname) .and. curr%var%is_set) then - found = .true. - var => curr%var - exit - endif - endif - curr => curr%next - end do - if (.not.found ) then - call errorHandle("PIO ERROR: PIO file not open.\nError getting metadata for variable "//trim(varname)//" in file "//trim(filename)//".",-999) - endif - - ! TODO: Maybe we shouldn't throw an error when the metadata isn't there, maybe we provide an output like ierr as an integer? - ! That way we can query for a metadata and on the EAMxx side decide what to do if the metadata is missing. - ierr = PIO_get_att(pio_file%pioFileDesc, var%piovar, metaname, metaval) - if (ierr .ne. 0) then - call errorHandle("Error getting attribute '" // trim(metaname) & - // "' on variable '" // trim(varname) & - // "' in pio file " // trim(filename) // ".", -999) - endif - end function get_variable_metadata_char -!=====================================================================! - ! Update the time dimension for a specific PIO file. This is needed when - ! reading or writing multiple time levels. Unlimited dimensions are treated - ! differently in netCDF than typical static length variables. Note, here - ! "time" is hardcoded as the only unlimited variable. If, in the future, - ! scream decides to allow for other "unlimited" dimensions to be used our - ! input/output than this routine will need to be adjusted. - subroutine eam_update_time(filename,time) - use pio, only: PIO_put_var - - character(len=*), intent(in) :: filename ! PIO filename - real(c_double), intent(in) :: time - - type(hist_var_t), pointer :: var - type(pio_atm_file_t),pointer :: pio_atm_file - integer :: ierr - logical :: found - - call lookup_pio_atm_file(filename,pio_atm_file,found) - pio_atm_file%numRecs = pio_atm_file%numRecs + 1 - call get_var(pio_atm_file,'time',var) - ! Only update time on the file if a valid time is provided - if (time>=0) then - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,(/ pio_atm_file%numRecs /), (/ 1 /), (/ time /)) - call errorHandle("PIO ERROR: something went wrong while writing time var on file="//trim(filename),ierr) - endif - end subroutine eam_update_time -!=====================================================================! - ! Assign institutions to header metadata for a specific pio output file. - subroutine eam_pio_createHeader(File) - use pio, only: PIO_put_att - - type(file_desc_t), intent(in) :: File ! Pio file Handle - integer :: retval - - ! We are able to have EAMxx directly set most attributes in the HEADER - ! except the list of institutions which appears to have a string that is too - ! long to accomodate using `set_str_attribute` as it is currently defined. - ! So we keep the setting of institutions here. - ! TODO: revise the set_str_attribute code to allow the - ! scream_output_manager.cpp to handle institutions too. - ! NOTE: The use of //char(10)// causes each institution to be written on it's own line, makes it easier to read. - retval=pio_put_att (File, PIO_GLOBAL, 'institutions', 'LLNL (Lawrence Livermore National Laboratory, Livermore, CA 94550, USA);' & - //char(10)//'ANL (Argonne National Laboratory, Argonne, IL 60439, USA); ' & - //char(10)//'BNL (Brookhaven National Laboratory, Upton, NY 11973, USA);' & - //char(10)//'LANL (Los Alamos National Laboratory, Los Alamos, NM 87545, USA);' & - //char(10)//'LBNL (Lawrence Berkeley National Laboratory, Berkeley, CA 94720, USA);' & - //char(10)//'ORNL (Oak Ridge National Laboratory, Oak Ridge, TN 37831, USA);' & - //char(10)//'PNNL (Pacific Northwest National Laboratory, Richland, WA 99352, USA);' & - //char(10)//'SNL (Sandia National Laboratories, Albuquerque, NM 87185, USA).' & - //char(10)//'Mailing address: LLNL Climate Program, c/o David C. Bader, Principal Investigator, L-103, 7000 East Avenue, Livermore, CA 94550, USA') - - end subroutine eam_pio_createHeader -!=====================================================================! - ! Ensures a pio system is in place, by either creating a new one - ! or getting the one created by CIME (for CIME builds) - subroutine eam_init_pio_subsystem(mpicom,atm_id) -#ifdef SCREAM_CIME_BUILD - use shr_pio_mod, only: shr_pio_getrearranger, shr_pio_getiosys, & - shr_pio_getiotype, shr_pio_getioformat -#else - use pio_types, only: pio_rearr_subset, PIO_iotype_netcdf, PIO_64BIT_DATA - use pio, only: pio_init - - integer :: ierr, stride, atm_rank, atm_size, num_aggregator -#endif - - integer, intent(in) :: mpicom - integer, intent(in) :: atm_id - - if (associated(pio_subsystem)) call errorHandle("PIO ERROR: local pio_subsystem pointer has already been established.",-999) - - atm_mpicom = mpicom - -#ifdef SCREAM_CIME_BUILD - pio_subsystem => shr_pio_getiosys(atm_id) - pio_iotype = shr_pio_getiotype(atm_id) - pio_rearranger = shr_pio_getrearranger(atm_id) - pio_mode = shr_pio_getioformat(atm_id) -#else - ! WARNING: we're assuming *every atm rank* is an I/O rank - call MPI_Comm_rank(atm_mpicom, atm_rank, ierr) - call MPI_Comm_size(atm_mpicom, atm_size, ierr) - - ! Just for removing unused dummy warnings - if (.false.) print *, atm_id - - stride = 1 - num_aggregator = 0 - - allocate(pio_subsystem) - pio_rearranger = pio_rearr_subset - pio_iotype = PIO_iotype_netcdf - pio_mode = PIO_64BIT_DATA ! Default to 64 bit - call PIO_init(atm_rank, atm_mpicom, atm_size, num_aggregator, stride, & - pio_rearr_subset, pio_subsystem, base=0) -#endif - - ! Init the list of pio files so that begin==end==null - pio_file_list_back => null() - pio_file_list_front => null() - - ! Init the iodecomp - iodesc_list_top => null() - - end subroutine eam_init_pio_subsystem -!=====================================================================! - ! Query whether the pio subsystem is inited already - ! This can be useful to avoid double-init or double-finalize calls. - function is_eam_pio_subsystem_inited() result(is_it) bind(c) - use iso_c_binding, only: c_bool - - logical(kind=c_bool) :: is_it - - is_it = LOGICAL(associated(pio_subsystem),kind=c_bool) - end function is_eam_pio_subsystem_inited -!=====================================================================! - function scream_iotype_to_pio_iotype(siotype) result(piotype) - integer, intent(in) :: siotype - integer :: piotype - - if(siotype == 0) then - piotype = pio_iotype - else if(siotype == 1) then - piotype = pio_iotype_netcdf - else if(siotype == 2) then - piotype = pio_iotype_pnetcdf - else if(siotype == 3) then - piotype = pio_iotype_adios - else - piotype = pio_iotype - end if - end function scream_iotype_to_pio_iotype -!=====================================================================! - ! Create a pio netCDF file with the appropriate name. - subroutine eam_pio_createfile(File,fname,iotype) - use pio, only: pio_createfile - use pio_types, only: pio_clobber - - type(file_desc_t), intent(inout) :: File ! Pio file Handle - character(len=*), intent(in) :: fname ! Pio file name - integer, intent(in) :: iotype - !-- - integer :: retval ! PIO error return value - integer :: mode ! Mode for how to handle the new file - integer :: piotype - - mode = ior(pio_mode,pio_clobber) ! Set to CLOBBER for now, TODO: fix to allow for optional mode type like in CAM - piotype = scream_iotype_to_pio_iotype(iotype) - retval = pio_createfile(pio_subsystem,File,piotype,fname,mode) - call errorHandle("PIO ERROR: unable to create file: "//trim(fname),retval) - - end subroutine eam_pio_createfile -!=====================================================================! - ! Open an already existing netCDF file. - subroutine eam_pio_openfile(pio_file,fname,iotype) - use pio, only: pio_openfile - use pio_types, only: pio_write, pio_nowrite - - type(pio_atm_file_t), pointer, intent(in) :: pio_file ! Pointer to pio file struct associated with this filename - character(len=*), intent(in) :: fname ! Pio file name - !-- - integer :: retval ! PIO error return value - integer :: mode ! Mode for how to handle the new file - integer, intent(in) :: iotype - - integer :: piotype - - if (is_read(pio_file%purpose)) then - mode = pio_nowrite - else - mode = pio_write - endif - piotype = scream_iotype_to_pio_iotype(iotype) - retval = pio_openfile(pio_subsystem,pio_file%pioFileDesc,piotype,fname,mode) - call errorHandle("PIO ERROR: unable to open file: "//trim(fname),retval) - - if (is_append(pio_file%purpose)) then - pio_file%is_enddef = .true. - endif - - end subroutine eam_pio_openfile -!=====================================================================! - ! Close a netCDF file. To be done as a last step after all input or output - ! for that file has been finished. - subroutine eam_pio_closefile(fname) - use pio, only: PIO_syncfile, PIO_closefile - - character(len=*), intent(in) :: fname ! Pio file name - !-- - type(pio_atm_file_t),pointer :: pio_atm_file - type(pio_file_list_t), pointer :: pio_file_list_ptr - logical :: found - type(hist_var_list_t), pointer :: curr_var_list - type(hist_var_t), pointer :: var - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(fname),pio_atm_file,found,pio_file_list_ptr) - - if (found) then - if (pio_atm_file%num_customers .eq. 1) then - if ( is_write(pio_atm_file%purpose) ) then - call PIO_syncfile(pio_atm_file%pioFileDesc) - endif - call PIO_closefile(pio_atm_file%pioFileDesc) - pio_atm_file%num_customers = pio_atm_file%num_customers - 1 - - ! Remove all variables from this file as customers for the stored pio - ! decompostions - curr_var_list => pio_atm_file%var_list_top ! Start with the first variable in the file - do while (associated(curr_var_list)) - var => curr_var_list%var ! The actual variable pointer - if (associated(var)) then - if (associated(var%iodesc_list)) then - ! Remove this variable as a customer of the associated iodesc - var%iodesc_list%num_customers = var%iodesc_list%num_customers - 1 - ! Dellocate select memory from this variable. Note we can't just - ! deallocate the whole var structure because this would also - ! deallocate the iodesc_list. - call deallocate_hist_var_t(var) - end if ! associated(var%iodesc_list) - end if ! associated(var) - curr_var_list => curr_var_list%next ! Move on to the next variable - end do ! associated(curr_var_list) - - ! Adjust pointers in the pio file list - if (associated(pio_file_list_ptr%prev)) then - pio_file_list_ptr%prev%next => pio_file_list_ptr%next - else - ! We're deleting the first item in the lists. Update pio_file_list_front - pio_file_list_front => pio_file_list_ptr%next - endif - if (associated(pio_file_list_ptr%next)) then - pio_file_list_ptr%next%prev => pio_file_list_ptr%prev - else - ! We're deleting the last item in the lists. Update pio_file_list_back - pio_file_list_back => pio_file_list_ptr%prev - endif - - ! Now that we have closed this pio file and purged it from the list we - ! can deallocate the structure. - deallocate(pio_atm_file) - else if (pio_atm_file%num_customers .gt. 1) then - pio_atm_file%num_customers = pio_atm_file%num_customers - 1 - else - call errorHandle("PIO ERROR: while closing file: "//trim(fname)//", found num_customers<=0",-999) - endif - else - call errorHandle("PIO ERROR: unable to close file: "//trim(fname)//", was not found",-999) - end if - - ! Final step, free any pio decompostion memory that is no longer needed. - ! Update: We are trying to reuse decompostions maximally, so we're - ! skipping this step. - !call free_decomp() - - end subroutine eam_pio_closefile -!=====================================================================! - ! Flushes IO buffers to file - subroutine eam_pio_flush_file(fname) - use pio, only: PIO_syncfile - - character(len=*), intent(in) :: fname ! Pio file name - !-- - type(pio_atm_file_t),pointer :: pio_atm_file - logical :: found - - ! Find the pointer for this file - call lookup_pio_atm_file(trim(fname),pio_atm_file,found) - - if (found) then - if ( is_write(pio_atm_file%purpose) .or. is_append(pio_atm_file%purpose) ) then - call PIO_syncfile(pio_atm_file%pioFileDesc) - else - call errorHandle("PIO ERROR: unable to flush file: "//trim(fname)//", is not open in write mode",-999) - endif - else - call errorHandle("PIO ERROR: unable to flush file: "//trim(fname)//", was not found",-999) - end if - end subroutine eam_pio_flush_file -!=====================================================================! - ! Helper function to debug list of decomps - subroutine print_decomp() - type(iodesc_list_t), pointer :: iodesc_ptr - - integer :: total - integer :: cnt - logical :: assoc - - if (associated(iodesc_list_top)) then - total = 0 - cnt = 0 - write(*,*) " PRINT DECOMP " - write(*,*) " ------------ " - write(*,'(8X,A10,A15,A50,A15)') "Location", "Associated?", "IODESC TAG", "# Customers" - else - write(*,*) "No DECOMP List to print" - return - end if - iodesc_ptr => iodesc_list_top - do while(associated(iodesc_ptr)) - total = total + 1 - assoc = .false. - if (associated(iodesc_ptr%iodesc).and.iodesc_ptr%iodesc_set) then - cnt = cnt + 1 - assoc = .true. - end if - write(*,'(I3,A5,I10,L15,A50,I15)') total, ": ", iodesc_ptr%location, assoc , trim(iodesc_ptr%tag), iodesc_ptr%num_customers - iodesc_ptr => iodesc_ptr%next - end do - write(*,*) " total: ", total, ", cnt: ",cnt - write(*,*) " ------------ " - - end subroutine print_decomp -!=====================================================================! - ! Free the memory stored in a hist_var_t derived type - subroutine deallocate_hist_var_t(var) - - type(hist_var_t), pointer :: var - - deallocate(var%compdof) - deallocate(var%dimid) - deallocate(var%dimlen) - var%is_set = .false. - - end subroutine deallocate_hist_var_t - !=====================================================================! - ! Free pio decomposition memory in PIO for any decompositions that are no - ! longer needed. Previously, we thought that this is an important memory - ! management step that should be taken whenever a file is closed. Now we're - ! trying to keep decomps persistent so they can be reused. Thus, calling - ! this routine is optional. - subroutine free_decomp() - use pio, only: PIO_freedecomp - type(iodesc_list_t), pointer :: iodesc_ptr, next - - ! Free all decompositions from PIO - iodesc_ptr => iodesc_list_top - do while(associated(iodesc_ptr)) - next => iodesc_ptr%next - if (associated(iodesc_ptr%iodesc).and.iodesc_ptr%iodesc_set) then - if (iodesc_ptr%num_customers .eq. 0) then - ! Free decomp - call pio_freedecomp(pio_subsystem,iodesc_ptr%iodesc) - ! Nullify this decomp - ! If we are at iodesc_list_top we need to make iodesc_ptr%next the new - ! iodesc_list_top: - if (associated(iodesc_ptr%prev)) then - iodesc_ptr%prev%next => iodesc_ptr%next - else - ! We are deleting the first item in the list, update - ! iodesc_list_front - iodesc_list_top => iodesc_ptr%next - end if - if (associated(iodesc_ptr%next)) then - iodesc_ptr%next%prev => iodesc_ptr%prev - end if - deallocate(iodesc_ptr) - end if - end if - iodesc_ptr => next - end do - - end subroutine free_decomp -!=====================================================================! - ! Finalize a PIO session within scream. Close all open files and deallocate - ! the pio_subsystem session. - subroutine eam_pio_finalize() - use pio, only: PIO_finalize, pio_freedecomp - ! May not be needed, possibly handled by PIO directly. - -#if !defined(SCREAM_CIME_BUILD) - integer :: ierr -#endif - type(pio_file_list_t), pointer :: curr_file_ptr, prev_file_ptr - type(iodesc_list_t), pointer :: iodesc_ptr - - ! Close all the PIO Files - curr_file_ptr => pio_file_list_front - do while (associated(curr_file_ptr)) - call eam_pio_closefile(curr_file_ptr%pio_file%filename) - prev_file_ptr => curr_file_ptr - curr_file_ptr => curr_file_ptr%next - deallocate(prev_file_ptr) - pio_file_list_front => curr_file_ptr ! be sure not to iterate over deallocated item - end do - ! Free all decompositions from PIO - iodesc_ptr => iodesc_list_top - do while(associated(iodesc_ptr)) - if (associated(iodesc_ptr%iodesc).and.iodesc_ptr%iodesc_set) then - call pio_freedecomp(pio_subsystem,iodesc_ptr%iodesc) - end if - iodesc_ptr => iodesc_ptr%next - end do - -#if !defined(SCREAM_CIME_BUILD) - call PIO_finalize(pio_subsystem, ierr) - call errorHandle("PIO ERROR: something went wrong when calling PIO_finalize.",ierr) - nullify(pio_subsystem) -#endif - - end subroutine eam_pio_finalize -!=====================================================================! - ! Handle any errors that occur in this module and print to screen an error - ! message. - subroutine errorHandle(errMsg, retVal) - use iso_c_binding - implicit none - - interface - subroutine finalize_scream_session() bind(C) - ! No inputs or anything, just interface to C code. - end subroutine finalize_scream_session - end interface - - character(len=*), intent(in) :: errMsg - integer, intent(in) :: retVal - - integer :: ierr - - if (retVal .ne. PIO_NOERR) then - write(*,'(I8,2x,A512)') retVal,trim(errMsg) - ! Kill run - call eam_pio_finalize() - call finalize_scream_session() - call mpi_abort(atm_mpicom,retVal,ierr) - end if - - end subroutine errorHandle -!=====================================================================! - ! Determine the unique pio_decomposition for this output grid, if it hasn't - ! been defined create a new one. - subroutine get_decomp(tag,dtype,dimension_len,compdof,iodesc_list) - use pio, only: pio_initdecomp - ! TODO: CAM code creates the decomp tag for the user. Theoretically it is - ! unique because it is based on dimensions and datatype. But the tag ends - ! up not being very descriptive. The todo item is to revisit how tags are - ! handled and decide if we want the code to create a tag or let the use - ! assign a tag. - character(len=*) :: tag ! Unique tag string describing this output grid - integer, intent(in) :: dtype ! Datatype associated with the output - integer, intent(in) :: dimension_len(:) ! Array of the dimension lengths for this decomp - integer(kind=pio_offset_kind), intent(in) :: compdof(:) ! The degrees of freedom this rank is responsible for - type(iodesc_list_t), pointer :: iodesc_list ! The pio decomposition list that holds this iodesc - - logical :: found ! Whether a decomp has been found among the previously defined decompositions - type(iodesc_list_t),pointer :: curr, prev ! Used to toggle through the recursive list of decompositions - integer :: loc_len ! Used to keep track of how many dimensions there are in decomp - - ! Assign a PIO decomposition to variable, if none exists, create a new one: - found = .false. - curr => iodesc_list_top - prev => iodesc_list_top - ! Cycle through all current iodesc to see if the decomp has already been - ! created - do while(associated(curr) .and. (.not.found)) - if (trim(tag) == trim(curr%tag)) then - found = .true. - else - prev => curr - curr => curr%next - end if - end do - ! If we didn't find an iodesc then we need to create one - if (.not.found) then - curr => prev ! Go back and allocate the new iodesc in curr%next - ! We may have no iodesc to begin with, so we need to associate the - ! beginning of the list. - if(.not.associated(curr)) then - allocate(curr) - curr%location = 1 - iodesc_list_top => curr - end if - if(associated(curr%iodesc)) then - allocate(curr%next) - curr%next%prev => curr - curr => curr%next - nullify(curr%iodesc) ! Extra step to ensure clean iodesc - nullify(curr%next) ! Extra step to ensure clean iodesc - end if - allocate(curr%iodesc) - curr%tag = trim(tag) - if (associated(curr%prev)) then - curr%location = prev%location+1 - end if - loc_len = size(dimension_len) - if ( loc_len.eq.1 .and. dimension_len(loc_len).eq.0 ) then - allocate(curr%iodesc) - else - call pio_initdecomp(pio_subsystem, dtype, dimension_len, compdof, curr%iodesc, rearr=pio_rearranger) - curr%iodesc_set = .true. - end if - curr%num_customers = 0 - end if - iodesc_list => curr - - end subroutine get_decomp -!=====================================================================! - ! Set the degrees of freedom (dof) this MPI rank is responsible for - ! reading/writing from/to file. - ! Briefly, PIO distributes the work of reading/writing the total array - ! of any variable data over all of the MPI ranks assigned to PIO. - ! DOF's are assigned considering a 1D flattening of any array, ignoring - ! unlimited dimensions, which are handled elsewhere. - ! Once it is known which dof a rank is responsible for this routine is used to - ! set those locally for use with all read and write statements. - ! Arguments: - ! filename: name of PIO input/output file. - ! varname: shortname of variable to set the dof for. - ! dof_len: number of dof associated for this rank and this variable - ! dof_vec: a vector of length dof_len which includes the global indices of - ! the flattened "varname" array that this MPI rank is responsible - ! for. - ! --- Example: If variable F has dimensions (x,y,t) of size (2,5,t), which means a --- - ! total of 2x5=10 dof, not counting the time dimension. - ! If 3 MPI ranks are used then a typical breakdown for dof might be: - ! Rank 1: (1,2,3) - ! Rank 2: (4,5,6) - ! Rank 3: (7,8,9,10) - subroutine set_dof(filename,varname,dof_len,dof_vec) - character(len=*), intent(in) :: filename - character(len=*), intent(in) :: varname - integer, intent(in) :: dof_len - integer(kind=pio_offset_kind), intent(in) :: dof_vec(dof_len) - - type(pio_atm_file_t),pointer :: pio_atm_file - type(hist_var_t), pointer :: var - logical :: found - integer :: ii - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - if (allocated(var%compdof)) deallocate(var%compdof) - allocate( var%compdof(dof_len) ) - do ii = 1,dof_len - var%compdof(ii) = dof_vec(ii) - end do - - end subroutine set_dof -!=====================================================================! - ! Get and assign all pio decompositions for a specific PIO file. This is a - ! mandatory step to be taken after all dimensions and variables have been - ! registered with an input or output file. - subroutine set_decomp(filename) - - character(len=*) :: filename ! Name of the pio file to set decomp for - - type(pio_atm_file_t), pointer :: current_atm_file - type(hist_var_list_t), pointer :: curr ! Used to cycle through recursive list of variables - type(hist_var_t), pointer :: hist_var ! Pointer to the variable structure that has been found - integer :: loc_len - logical :: found - - call lookup_pio_atm_file(filename,current_atm_file,found) - if (.not. found) then - call errorHandle("PIO ERROR: pio file '"//trim(filename)//"' not found.",999) - endif - - curr => current_atm_file%var_list_top - do while (associated(curr)) - ! Skip already deallocated vars, and vars for which decomp was already set - ! NOTE: fortran does not mandate/prohibit logical op short circuit, so do the - ! two following if statements separately - if (associated(curr%var)) then - if (.not. associated(curr%var%iodesc)) then - hist_var => curr%var - if (.not.associated(hist_var)) call errorHandle("PIO ERROR: unable to set decomp for file, var: "//trim(current_atm_file%filename)//", "//trim(hist_var%name)//". Set DOF.",999) - ! Assign decomp - if (hist_var%has_t_dim) then - loc_len = max(1,hist_var%numdims-1) - call get_decomp(hist_var%pio_decomp_tag,hist_var%dtype,hist_var%dimlen(:loc_len),hist_var%compdof,hist_var%iodesc_list) - else - call get_decomp(hist_var%pio_decomp_tag,hist_var%dtype,hist_var%dimlen,hist_var%compdof,hist_var%iodesc_list) - end if - hist_var%iodesc => hist_var%iodesc_list%iodesc - hist_var%iodesc_list%num_customers = hist_var%iodesc_list%num_customers + 1 ! Add this variable as a customer of this pio decomposition - endif - end if - curr => curr%next - end do - - end subroutine set_decomp -!=====================================================================! - ! Query the hist_var_t pointer for a specific variable on a specific file. - subroutine get_var(pio_file,varname,var) - - type(pio_atm_file_t), pointer :: pio_file ! Pio output file structure - character(len=*) :: varname ! Name of the variable to query - type(hist_var_t), pointer :: var ! Pointer to the variable structure that has been found - - type(hist_var_list_t), pointer :: curr ! Used to cycle through recursive list of variables - - curr => pio_file%var_list_top - do while (associated(curr)) - var => curr%var - if (associated(var)) then - if (trim(varname) == trim(var%name) .and. var%is_set) return - end if - curr => curr%next - end do - - ! If we got this far we didn't find the variable - call errorHandle("PIO ERROR: unable to find variable: "//trim(varname)//" in file: "//trim(pio_file%filename),999) - - end subroutine get_var -!=====================================================================! - ! Lookup pointer for pio file based on filename. - subroutine lookup_pio_atm_file(filename,pio_file,found,pio_file_list_ptr_in) - - character(len=*),intent(in) :: filename ! Name of file to be found - type(pio_atm_file_t), pointer :: pio_file ! Pointer to pio_atm_output structure associated with this filename - logical, intent(out) :: found ! whether or not the file was found - type(pio_file_list_t), pointer, optional :: pio_file_list_ptr_in - - type(pio_file_list_t), pointer :: pio_file_list_ptr - - ! Scan pio file list, search for this filename - found = .false. - pio_file_list_ptr => pio_file_list_front - pio_file => null() - do while (associated(pio_file_list_ptr)) - if (trim(filename)==trim(pio_file_list_ptr%pio_file%filename)) then - pio_file => pio_file_list_ptr%pio_file - found = .true. - if (present(pio_file_list_ptr_in)) then - pio_file_list_ptr_in => pio_file_list_ptr - endif - return - end if - pio_file_list_ptr => pio_file_list_ptr%next - end do - - end subroutine lookup_pio_atm_file -!=====================================================================! - ! Create a new pio file pointer based on filename. - subroutine get_pio_atm_file(filename,pio_file,purpose,iotype) - use pio, only: PIO_inq_dimid, PIO_inq_dimlen - - character(len=*),intent(in) :: filename ! Name of file to be found - type(pio_atm_file_t), pointer :: pio_file ! Pointer to pio_atm_output structure associated with this filename - integer,intent(in) :: purpose ! Purpose for this file lookup, 0 = find already existing, 1 = create new as output, 2 = open new as input - integer,intent(in) :: iotype - - logical :: found - type(pio_file_list_t), pointer :: new_list_item - - integer :: ierr, time_id - - ! Sanity checks - if ( .not. (is_read(purpose) .or. is_write(purpose) .or. is_append(purpose)) ) then - call errorHandle("PIO Error: unrecognized open mode requested for file '"//filename//"'.",-999) - endif - if ( is_read(purpose) .and. is_write(purpose) ) then - call errorHandle("PIO Error: both READ and WRITE mode requested for file '"//filename//"'.",-999) - endif - if ( is_read(purpose) .and. is_append(purpose) ) then - call errorHandle("PIO Error: APPEND mode requested along with READ mode for file '"//filename//"'.",-999) - endif - - ! If the file already exists, return that file - call lookup_pio_atm_file(trim(filename),pio_file,found) - if (found) then - if (is_write(purpose) .or. is_write(pio_file%purpose) ) then - ! We only allow multiple customers of the file if they all use it in read mode. - call errorHandle("PIO Error: file '"//trim(filename)//"' was already open for writing.",-999) - endif - pio_file%num_customers = pio_file%num_customers + 1 - else - allocate(new_list_item) - allocate(new_list_item%pio_file) - pio_file => new_list_item%pio_file - - ! Create and initialize the new pio file: - pio_file%filename = trim(filename) - pio_file%numRecs = 0 - pio_file%num_customers = 1 - pio_file%purpose = purpose - if (is_read(purpose) .or. is_append(purpose)) then - ! Either read or append to existing file. Either way, file must exist on disk - call eam_pio_openfile(pio_file,trim(pio_file%filename),iotype) - ! Update the numRecs to match the number of recs in this file. - ierr = pio_inq_dimid(pio_file%pioFileDesc,"time",time_id) - if (ierr.ne.0) then - ! time is not present in the file, set the number of Recs to 1 - pio_file%numRecs = 1 - else - ! time is present, set the number of Recs accordingly - ierr = pio_inq_dimlen(pio_file%pioFileDesc,time_id,pio_file%numRecs) - call errorHandle("EAM_PIO ERROR: Unable to determine length for dimension time in file "//trim(pio_file%filename),ierr) - end if - elseif (is_write(purpose)) then - ! New output file - call eam_pio_createfile(pio_file%pioFileDesc,trim(pio_file%filename),iotype) - call eam_pio_createHeader(pio_file%pioFileDesc) - else - call errorHandle("PIO Error: get_pio_atm_file with filename = "//trim(filename)//", purpose (int) assigned to this lookup is not valid" ,-999) - end if - - ! Update the pio file list - if (associated(pio_file_list_back)) then - ! 1) Link new file to the new_list_itement back of the list - new_list_item%prev => pio_file_list_back - ! 2) Link the current last element of the list to the new one - pio_file_list_back%next => new_list_item - ! 3) and update the pointer to the last - pio_file_list_back => new_list_item - else - ! The list was empty. Set both front/back to point to the new item - pio_file_list_front => new_list_item - pio_file_list_back => new_list_item - endif - endif - end subroutine get_pio_atm_file -!=====================================================================! - ! Retrieve the time value for a specific time_index - ! If the input arg time_index is not provided, then it is assumed the user wants - ! the last time entry. If time_index is present, it MUST be valid - function read_time_at_index(filename,time_index) result(val) - use pio, only: PIO_get_var, PIO_inq_varid, PIO_inq_dimid, PIO_inq_dimlen - - character(len=*), intent(in) :: filename - integer, intent(in), optional :: time_index - real(c_double) :: val - real(c_double) :: val_buf(1) - - type(pio_atm_file_t), pointer :: pio_atm_file - logical :: found - integer :: dim_id, time_len, ierr - type(var_desc_t) :: varid ! netCDF variable ID - integer :: strt(1), cnt(1), timeidx - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - if (.not.found) call errorHandle("read_time_at_index ERROR: File "//trim(filename)//" not found",-999) - ierr = PIO_inq_varid(pio_atm_file%pioFileDesc,"time",varid) - call errorHandle('read_time_at_index: Error finding variable ID for "time" in file '//trim(filename)//'.',ierr); - - ierr = pio_inq_dimid(pio_atm_file%pioFileDesc,trim("time"),dim_id) - call errorHandle("read_time_at_index ERROR: dimension 'time' not found in file "//trim(filename)//".",ierr) - ierr = pio_inq_dimlen(pio_atm_file%pioFileDesc,dim_id,time_len) - call errorHandle("PIO Error! Something went wrong when inquiring time dim length on file file "//trim(filename)//".",ierr) - - if (present(time_index)) then - timeidx = time_index - else - timeidx = time_len - endif - if (timeidx .gt. time_len) then - call errorHandle("read_time_at_index ERROR: time_index arg larger than length of time dimension",-999) - elseif (timeidx .le. 0) then - call errorHandle("read_time_at_index ERROR: time_index arg must be positive",-999) - end if - - strt(1) = timeidx - cnt(1) = 1 - ierr = PIO_get_var(pio_atm_file%pioFileDesc,varid,strt,cnt,val_buf) - call errorHandle('read_time_at_index: Error reading variable "time" in file '//trim(filename)//'.',ierr); - val = val_buf(1) - end function read_time_at_index -!=====================================================================! - ! Write output to file based on type (int or real) - ! --Note-- that any dimensionality could be written if it is flattened to 1D - ! before calling a write routine. - ! Mandatory inputs are: - ! filename: the netCDF filename to be written to. - ! varname: The shortname for the variable being written. - ! var_data_ptr: An array of data that will be used to write the output. NOTE: - ! If PIO MPI ranks > 1, var_data_ptr should be the subset of the global array - ! which includes only those degrees of freedom that have been - ! assigned to this rank. See set_dof above. - !--------------------------------------------------------------------------- - ! - ! grid_write_darray_1d: Write a variable defined on this grid - ! - !--------------------------------------------------------------------------- - subroutine grid_write_darray_float(filename, varname, buf, buf_size) - use pio, only: PIO_put_var, PIO_setframe, PIO_write_darray - use pio_types, only: PIO_max_var_dims - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer(kind=c_int), intent(in) :: buf_size - real(kind=c_float), intent(in) :: buf(buf_size) - - ! Local variables - - type(pio_atm_file_t), pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr,jdim - integer :: start(pio_max_var_dims), count(pio_max_var_dims) - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - - if (var%has_t_dim) then - ! Set the time index we are writing - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(max(1,pio_atm_file%numRecs),kind=pio_offset_kind)) - endif - - if (var%is_partitioned) then - call pio_write_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - else - if (var%has_t_dim) then - do jdim=1,var%numdims - if (var%dimid(jdim) .eq. time_dimid) then - start (jdim) = int(max(1,pio_atm_file%numRecs)) - count (jdim) = 1 - else - start (jdim) = 1 - count (jdim) = var%dimlen(jdim) - endif - enddo - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,start(:var%numdims),count(:var%numdims),buf) - else - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,buf) - endif - endif - - call errorHandle( 'eam_grid_write_darray_float: Error writing variable '//trim(varname),ierr) - end subroutine grid_write_darray_float - subroutine grid_write_darray_double(filename, varname, buf, buf_size) - use pio, only: PIO_put_var, PIO_setframe, PIO_write_darray - use pio_types, only: PIO_max_var_dims - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer(kind=c_int), intent(in) :: buf_size - real(kind=c_double), intent(in) :: buf(buf_size) - - ! Local variables - - type(pio_atm_file_t), pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr,jdim - integer :: start(pio_max_var_dims), count(pio_max_var_dims) - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - - if (var%has_t_dim) then - ! Set the time index we are writing - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(max(1,pio_atm_file%numRecs),kind=pio_offset_kind)) - endif - - if (var%is_partitioned) then - call pio_write_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - else - if (var%has_t_dim) then - do jdim=1,var%numdims - if (var%dimid(jdim) .eq. time_dimid) then - start (jdim) = int(max(1,pio_atm_file%numRecs)) - count (jdim) = 1 - else - start (jdim) = 1 - count (jdim) = var%dimlen(jdim) - endif - enddo - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,start(:var%numdims),count(:var%numdims),buf) - else - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,buf) - endif - endif - - call errorHandle( 'eam_grid_write_darray_double: Error writing variable '//trim(varname),ierr) - end subroutine grid_write_darray_double - subroutine grid_write_darray_int(filename, varname, buf, buf_size) - use pio, only: PIO_put_var, PIO_setframe, PIO_write_darray - use pio_types, only: PIO_max_var_dims - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer(kind=c_int), intent(in) :: buf_size - integer(kind=c_int), intent(in) :: buf(buf_size) - - ! Local variables - - type(pio_atm_file_t), pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr,jdim - integer :: start(pio_max_var_dims), count(pio_max_var_dims) - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - - if (var%has_t_dim) then - ! Set the time index we are writing - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(max(1,pio_atm_file%numRecs),kind=pio_offset_kind)) - endif - - if (var%is_partitioned) then - call pio_write_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - else - if (var%has_t_dim) then - do jdim=1,var%numdims - if (var%dimid(jdim) .eq. time_dimid) then - start (jdim) = int(max(1,pio_atm_file%numRecs)) - count (jdim) = 1 - else - start (jdim) = 1 - count (jdim) = var%dimlen(jdim) - endif - enddo - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,start(:var%numdims),count(:var%numdims),buf) - else - ierr = pio_put_var(pio_atm_file%pioFileDesc,var%piovar,buf) - endif - endif - - call errorHandle( 'eam_grid_write_darray_int: Error writing variable '//trim(varname),ierr) - end subroutine grid_write_darray_int -!=====================================================================! - ! Read output from file based on type (int or real) - ! --Note-- that any dimensionality could be read if it is flattened to 1D - ! before calling a write routine. - ! Mandatory inputs are: - ! filename: the netCDF filename to be read from. - ! varname: The shortname for the variable being read.i - ! var_data_ptr: An c_ptr of data that will be used to read the input. NOTE: - ! If PIO MPI ranks > 1, var_data_ptr should be the subset of the global array - ! which includes only those degrees of freedom that have been - ! assigned to this rank. See set_dof above. - ! time_index: The 1-based time index for this variable (0 is ignored). - !--------------------------------------------------------------------------- - ! - ! grid_read_darray_1d: Read a variable defined on this grid - ! - !--------------------------------------------------------------------------- - subroutine grid_read_darray_double(filename, varname, buf, buf_size, time_index) - use pio, only: PIO_setframe, PIO_read_darray - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer (kind=c_int), intent(in) :: buf_size - real(kind=c_double), intent(out) :: buf(buf_size) - integer, intent(in) :: time_index - - ! Local variables - type(pio_atm_file_t),pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr, var_size - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - ! Set the timesnap we are reading - if (time_index .gt. 0) then - ! The user has set a valid time index to read from - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(time_index,kind=pio_offset_kind)) - else - ! Otherwise default to the last time_index in the file - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(pio_atm_file%numRecs,kind=pio_offset_kind)) - end if - - ! We don't want the extent along the 'time' dimension - var_size = SIZE(var%compdof) - - ! Now we know the exact size of the array, and can shape the f90 pointer - call pio_read_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - call errorHandle( 'eam_grid_read_darray_double: Error reading variable '//trim(varname),ierr) - end subroutine grid_read_darray_double - subroutine grid_read_darray_float(filename, varname, buf, buf_size, time_index) - use pio, only: PIO_setframe, PIO_read_darray - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer (kind=c_int), intent(in) :: buf_size - real(kind=c_float), intent(out) :: buf(buf_size) - integer, intent(in) :: time_index - - ! Local variables - type(pio_atm_file_t),pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr, var_size - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - - ! Set the timesnap we are reading - if (time_index .gt. 0) then - ! The user has set a valid time index to read from - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(time_index,kind=pio_offset_kind)) - else - ! Otherwise default to the last time_index in the file - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(pio_atm_file%numRecs,kind=pio_offset_kind)) - end if - - ! We don't want the extent along the 'time' dimension - var_size = SIZE(var%compdof) - - ! Now we know the exact size of the array, and can shape the f90 pointer - call pio_read_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - call errorHandle( 'eam_grid_read_darray_float: Error reading variable '//trim(varname),ierr) - end subroutine grid_read_darray_float - subroutine grid_read_darray_int(filename, varname, buf, buf_size, time_index) - use pio, only: PIO_setframe, PIO_read_darray - - ! Dummy arguments - character(len=*), intent(in) :: filename ! PIO filename - character(len=*), intent(in) :: varname - integer (kind=c_int), intent(in) :: buf_size - integer (kind=c_int), intent(out) :: buf(buf_size) - integer, intent(in) :: time_index - - ! Local variables - type(pio_atm_file_t),pointer :: pio_atm_file - type(hist_var_t), pointer :: var - integer :: ierr, var_size - logical :: found - - call lookup_pio_atm_file(trim(filename),pio_atm_file,found) - call get_var(pio_atm_file,varname,var) - - ! Set the timesnap we are reading - if (time_index .gt. 0) then - ! The user has set a valid time index to read from - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(time_index,kind=pio_offset_kind)) - else - ! Otherwise default to the last time_index in the file - call PIO_setframe(pio_atm_file%pioFileDesc,var%piovar,int(pio_atm_file%numRecs,kind=pio_offset_kind)) - end if - - ! We don't want the extent along the 'time' dimension - var_size = SIZE(var%compdof) - - ! Now we know the exact size of the array, and can shape the f90 pointer - call pio_read_darray(pio_atm_file%pioFileDesc, var%piovar, var%iodesc, buf, ierr) - call errorHandle( 'eam_grid_read_darray_int: Error reading variable '//trim(varname),ierr) - end subroutine grid_read_darray_int -!=====================================================================! - subroutine convert_int_2_str(int_in,str_out) - integer, intent(in) :: int_in - character(len=*), intent(out) :: str_out - - character(len=8) :: fmt_str - - if (int_in < 0) then - fmt_str = "(A1," - else - fmt_str = "(" - end if - - if (abs(int_in)<10) then - fmt_str = trim(fmt_str)//"I1)" - elseif (abs(int_in)<1e2) then - fmt_str = trim(fmt_str)//"I2)" - elseif (abs(int_in)<1e3) then - fmt_str = trim(fmt_str)//"I3)" - elseif (abs(int_in)<1e4) then - fmt_str = trim(fmt_str)//"I4)" - elseif (abs(int_in)<1e5) then - fmt_str = trim(fmt_str)//"I5)" - elseif (abs(int_in)<1e6) then - fmt_str = trim(fmt_str)//"I6)" - elseif (abs(int_in)<1e7) then - fmt_str = trim(fmt_str)//"I7)" - elseif (abs(int_in)<1e8) then - fmt_str = trim(fmt_str)//"I8)" - elseif (abs(int_in)<1e9) then - fmt_str = trim(fmt_str)//"I9)" - endif - - if (int_in < 0) then - write(str_out,fmt_str) "n", int_in - else - write(str_out,fmt_str) int_in - end if - - end subroutine convert_int_2_str - - subroutine get_coord (filename,shortname,hist_coord) - character(len=256), intent(in) :: filename - character(len=256), intent(in) :: shortname - type(hist_coord_t), pointer, intent(out) :: hist_coord - - type(pio_atm_file_t), pointer :: pio_atm_file - type(hist_coord_list_t), pointer :: curr, prev - logical :: file_found, dim_found - - hist_coord => NULL() - - call lookup_pio_atm_file(trim(filename),pio_atm_file,file_found) - if (.not. file_found ) then - call errorHandle("PIO ERROR: error retrieving dimension "//trim(shortname)//" in file "//trim(filename)//".\n PIO file not found or not open.",-999) - endif - - curr => pio_atm_file%coord_list_top - do while (associated(curr)) - if (associated(curr%coord)) then - if(trim(curr%coord%name)==trim(shortname)) then - dim_found = .true. - exit - endif - end if - prev => curr - curr => prev%next - end do - - if (.not. dim_found ) then - call errorHandle("PIO ERROR: error retrieving dimension "//trim(shortname)//" from file "//trim(filename)//". Dimension not found.",-999) - endif - - hist_coord => curr%coord - end subroutine get_coord - - function is_read (purpose) - integer, intent(in) :: purpose - logical :: is_read - is_read = iand(purpose,file_purpose_in) .ne. 0 - end function is_read - function is_write (purpose) - integer, intent(in) :: purpose - logical :: is_write - is_write = iand(purpose,file_purpose_out) .ne. 0 - end function is_write - function is_append (purpose) - integer, intent(in) :: purpose - logical :: is_append - is_append = iand(purpose,file_purpose_app) .ne. 0 - end function is_append -!=====================================================================! -end module scream_scorpio_interface diff --git a/components/eamxx/src/share/io/scream_scorpio_interface.cpp b/components/eamxx/src/share/io/scream_scorpio_interface.cpp index ead1aa0fbd6..0d4918ad230 100644 --- a/components/eamxx/src/share/io/scream_scorpio_interface.cpp +++ b/components/eamxx/src/share/io/scream_scorpio_interface.cpp @@ -1,54 +1,125 @@ #include "scream_scorpio_interface.hpp" -#include "ekat/ekat_scalar_traits.hpp" +#include "scream_shr_interface_c2f.hpp" + #include "scream_config.h" -#include "ekat/ekat_assert.hpp" -#include "share/scream_types.hpp" +#include +#include #include -#include - - -using scream::Real; -using scream::Int; -extern "C" { - -// Fortran routines to be called from C++ - void register_file_c2f(const char*&& filename, const int& mode, const int& iotype); - int get_file_mode_c2f(const char*&& filename); - void set_decomp_c2f(const char*&& filename); - void set_dof_c2f(const char*&& filename,const char*&& varname,const Int dof_len,const std::int64_t *x_dof); - void grid_read_data_array_c2f_int(const char*&& filename, const char*&& varname, const Int time_index, int *buf, const int buf_size); - void grid_read_data_array_c2f_float(const char*&& filename, const char*&& varname, const Int time_index, float *buf, const int buf_size); - void grid_read_data_array_c2f_double(const char*&& filename, const char*&& varname, const Int time_index, double *buf, const int buf_size); - - void grid_write_data_array_c2f_int(const char*&& filename, const char*&& varname, const int* buf, const int buf_size); - void grid_write_data_array_c2f_float(const char*&& filename, const char*&& varname, const float* buf, const int buf_size); - void grid_write_data_array_c2f_double(const char*&& filename, const char*&& varname, const double* buf, const int buf_size); - void eam_init_pio_subsystem_c2f(const int mpicom, const int atm_id); - void eam_pio_finalize_c2f(); - void eam_pio_closefile_c2f(const char*&& filename); - void eam_pio_flush_file_c2f(const char*&& filename); - void pio_update_time_c2f(const char*&& filename,const double time); - void register_dimension_c2f(const char*&& filename, const char*&& shortname, const char*&& longname, const int global_length, const bool partitioned); - void register_variable_c2f(const char*&& filename, const char*&& shortname, const char*&& longname, - const char*&& units, const int numdims, const char** var_dimensions, - const int dtype, const int nc_dtype, const char*&& pio_decomp_tag); - void set_variable_metadata_char_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name, const char*&& meta_val); - void set_variable_metadata_float_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name, const float meta_val); - void set_variable_metadata_double_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name, const double meta_val); - float get_variable_metadata_float_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name); - double get_variable_metadata_double_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name); - void get_variable_metadata_char_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name, char*&& meta_val); - void eam_pio_redef_c2f(const char*&& filename); - void eam_pio_enddef_c2f(const char*&& filename); - bool is_enddef_c2f(const char*&& filename); -} // extern C +#include namespace scream { namespace scorpio { +// This class is an implementation detail, and therefore it is hidden inside +// a cpp file. All customers of IO capabilities must use the common interfaces +// exposed in the header file of this source file. +// This class simply serves as a container for persistent IO data. +struct ScorpioSession +{ +public: + static ScorpioSession& instance () { + static ScorpioSession s; + return s; + } + + template + using strmap_t = std::map; + + strmap_t files; + strmap_t> decomps; + + // In the above map, we would like to label decomps as dtype_dim1$N1_dim2$N2... + // where N$i is the global length of dim$i. However, it *may* happen that we use + // two different decompositions for the same global layout, which would clash + // the names. For this reason, we append at the end an increasing counter, + // which disambiguate between globally-equivalent partitions. + // When adding a new decomp, we check this map to see if another decomp + // already exists with the same global layout. If so, we first check to see + // if that decomp is equivalent to the new one *on all ranks*. If yes, we + // recycle it, otherwise we create a new PIO decomp. + // strmap_t> decomp_global_layout_to_decomp_name; + // strmap_t decomp_global_layout_to_counter; + + int pio_sysid = -1; + int pio_type_default = -1; + int pio_rearranger = -1; + int pio_format = -1; + + ekat::Comm comm; + +private: + + ScorpioSession () = default; +}; + +// --------------------------------------------------------------------------------------------- // + +template +void copy_data (const S* src, D* dst, int n) { + for (int i=0; i& e) +{ + return e->name; +} + +template +std::string print_map_keys (const std::map& map) { + std::string s; + for (const auto& it : map) { + s += it.first + ","; + } + s.pop_back(); + return s; +} + // Retrieve the int codes PIO uses to specify data types int nctype (const std::string& type) { if (type=="int") { @@ -65,637 +136,1351 @@ int nctype (const std::string& type) { #else return PIO_FLOAT; #endif + } else if (type=="char") { + return PIO_CHAR; } else { EKAT_ERROR_MSG ("Error! Unrecognized/unsupported data type '" + type + "'.\n"); } } -std::string nctype2str (const int type) { - for (auto t : {"int", "int64", "float", "double"}) { - if (nctype(t)==type) return t; + +template +int nctype () { + if (std::is_same::value) { + return PIO_INT; + } else if (std::is_same::value) { + return PIO_INT64; + } else if (std::is_same::value) { + return PIO_FLOAT; + } else if (std::is_same::value) { + return PIO_DOUBLE; + } else if (std::is_same::value) { + return PIO_CHAR; + } else { + EKAT_ERROR_MSG ("Error! Unrecognized/unsupported data type.\n"); } - return "UNKNOWN"; } -/* ----------------------------------------------------------------- */ -void eam_init_pio_subsystem(const ekat::Comm& comm) { - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - eam_init_pio_subsystem(fcomm); + +template +const void* ncdata (const T& v) { + return reinterpret_cast(&v); +} +template<> +const void* ncdata (const std::string& v) { + return reinterpret_cast(v.data()); } -void eam_init_pio_subsystem(const int mpicom, const int atm_id) { - // TODO: Right now the compid has been hardcoded to 0 and the flag - // to create a init a subsystem in SCREAM is hardcoded to true. - // When surface coupling is established we will need to refactor this - // routine to pass the appropriate values depending on if we are running - // the full model or a unit test. - eam_init_pio_subsystem_c2f(mpicom,atm_id); +template +PIO_Offset nclen (const T& v) { + return 1; +} +template<> +PIO_Offset nclen (const std::string& v) { + return v.size(); } -/* ----------------------------------------------------------------- */ -void eam_pio_finalize() { - eam_pio_finalize_c2f(); + +std::string refine_dtype (const std::string& dtype) { + if (dtype=="real") { +#if defined(SCREAM_DOUBLE_PRECISION) + return "double"; +#else + return "float"; +#endif + } else if (dtype=="single") { + return "float"; + } else { + return dtype; + } } -/* ----------------------------------------------------------------- */ -void register_file(const std::string& filename, const FileMode mode, const int iotype) { - register_file_c2f(filename.c_str(),mode,iotype); + +size_t dtype_size (const std::string& dtype) { + if (dtype=="int") { + return sizeof(int); + } else if (dtype=="int64") { + return sizeof(long long); + } else if (dtype=="float") { + return sizeof(float); + } else if (dtype=="double") { + return sizeof(double); + } else { + EKAT_ERROR_MSG ("Error! Unrecognized/unsupported data type '" + dtype + "'.\n"); + } + return 0; } -/* ----------------------------------------------------------------- */ -void eam_pio_closefile(const std::string& filename) { - eam_pio_closefile_c2f(filename.c_str()); +// ====================== Local utilities ========================== // + +namespace impl { +// Note: these utilities are used in this file to retrieve PIO entities, +// so that we implement all checks once (rather than in every function) + +// Small struct that allows to quickly open a file (in Read mode) if it wasn't open. +// If the file had to be open, when the struct is deleted, it will release the file. +struct PeekFile { + PeekFile(const std::string& filename_in) { + filename = filename_in; + was_open = is_file_open(filename); + if (not was_open) { + register_file(filename,Read); + } + auto& s = ScorpioSession::instance(); + file = &s.files[filename]; + } + + ~PeekFile () { + if (not was_open) { + // Note: this function _could_ throw, but it should not happen (unless someone + // else called release_file twice). That's b/c we either are not the only + // customer (so nothing to be done other than a ref count decrement) or + // the file was open in Read mode, in which case it doesn't need to do + // much in scorpio. + release_file(filename); + } + } + + const PIOFile* file; + std::string filename; + bool was_open; +}; + +PIOFile& get_file (const std::string& filename, + const std::string& context) +{ + auto& s = ScorpioSession::instance(); + + EKAT_REQUIRE_MSG (s.files.count(filename)==1, + "Error! Could not retrieve the file. File not open.\n" + " - filename: " + filename + "\n" + "Context:\n" + " " + context + "\n"); + + return s.files.at(filename); } -void eam_flush_file(const std::string& filename) { - eam_pio_flush_file_c2f(filename.c_str()); + +PIODim& get_dim (const std::string& filename, + const std::string& dimname, + const std::string& context) +{ + const auto& f = get_file(filename,context); + EKAT_REQUIRE_MSG (f.dims.count(dimname)==1, + "Error! Could not retrieve dimension. Dimension not found.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n" + " - dims on file: " + print_map_keys(f.dims) + "\n" + "Context:\n" + " " + context + "\n"); + + return *f.dims.at(dimname); } -/* ----------------------------------------------------------------- */ -void set_decomp(const std::string& filename) { - set_decomp_c2f(filename.c_str()); +PIOVar& get_var (const std::string& filename, + const std::string& varname, + const std::string& context) +{ + const auto& f = get_file(filename,context); + EKAT_REQUIRE_MSG (f.vars.count(varname)==1, + "Error! Could not retrieve variable. Variable not found.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n" + " - vars on file : " + print_map_keys(f.vars) + "\n" + "Context:\n" + " " + context + "\n"); + + return *f.vars.at(varname); } -/* ----------------------------------------------------------------- */ -int get_dimlen(const std::string& filename, const std::string& dimname) + +} // namespace impl + +// ====================== Global IO operations ======================= // + +void init_subsystem(const ekat::Comm& comm, const int atm_id) { - int ncid, dimid, err; - PIO_Offset len; + auto& s = ScorpioSession::instance(); + s.comm = comm; - bool was_open = is_file_open_c2f(filename.c_str(),-1); - if (not was_open) { - register_file(filename,Read); - } + EKAT_REQUIRE_MSG (s.pio_sysid==-1, + "Error! Attmept to re-initialize pio subsystem.\n"); - ncid = get_file_ncid_c2f (filename.c_str()); - err = PIOc_inq_dimid(ncid,dimname.c_str(),&dimid); - EKAT_REQUIRE_MSG (err!=PIO_EBADDIM, - "Error! Could not find dimension in the file.\n" - " - filename : " + filename + "\n" - " - dimname : " + dimname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while retrieving dimension id.\n" - " - filename : " + filename + "\n" - " - dimname : " + dimname + "\n" - " - pio error: " + std::to_string(err) + "\n"); +#ifdef SCREAM_CIME_BUILD + s.pio_sysid = shr_get_iosysid_c2f(atm_id); + s.pio_type_default = shr_get_iotype_c2f(atm_id); + s.pio_rearranger = shr_get_rearranger_c2f(atm_id); + s.pio_format = shr_get_ioformat_c2f(atm_id); +#else + // Use some reasonable defaults for standalone EAMxx tests + int stride = 1; + int base = 0; - err = PIOc_inq_dimlen(ncid,dimid,&len); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while querying dimension length.\n" - " - filename : " + filename + "\n" - " - dimname : " + dimname + "\n" - " - pio error: " + std::to_string(err) + "\n"); + s.pio_rearranger = PIO_REARR_SUBSET; + s.pio_format = PIO_64BIT_DATA; +#if PIO_USE_PNETCDF + s.pio_type_default = PIO_IOTYPE_PNETCDF; +#elif PIO_USE_NETCDF + s.pio_type_default = PIO_IOTYPE_NETCDF; +#else +#error "Standalone EAMxx requires either PNETCDF or NETCDF iotype to be available in Scorpio" +#endif - if (not was_open) { - eam_pio_closefile(filename); - } + auto err = PIOc_Init_Intracomm(comm.mpi_comm(), comm.size(), stride, base, s.pio_rearranger, &s.pio_sysid); + check_scorpio_noerr (err,"init_subsystem", "Init_Intracomm"); + + // Unused in standalone mode + (void) atm_id; +#endif + + static_assert (sizeof(offset_t)==sizeof(PIO_Offset), + "Error! PIO was configured with PIO_OFFSET not a 64-bit int.\n"); +} - return len; +bool is_subsystem_inited () { + return ScorpioSession::instance().pio_sysid!=-1; } -/* ----------------------------------------------------------------- */ -bool has_dim (const std::string& filename, const std::string& dimname) + +void finalize_subsystem () { - int ncid, dimid, err; + auto& s = ScorpioSession::instance(); - bool was_open = is_file_open_c2f(filename.c_str(),-1); - if (not was_open) { - register_file(filename,Read); - } + // TODO: should we simply return instead? I think trying to finalize twice + // *may* be a sign of possible bugs, though with Catch2 testing + // I *think* there may be some issue with how the code is run. + EKAT_REQUIRE_MSG (s.pio_sysid!=-1, + "Error! PIO subsystem was already finalized.\n"); - ncid = get_file_ncid_c2f (filename.c_str()); - err = PIOc_inq_dimid(ncid,dimname.c_str(),&dimid); - if (err==PIO_EBADDIM) { - return false; + 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(); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while retrieving dimension id.\n" - " - filename : " + filename + "\n" - " - dimname : " + dimname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - if (not was_open) { - eam_pio_closefile(filename); + for (auto& it : s.decomps) { + EKAT_REQUIRE_MSG (it.second.use_count()==1, + "Error! ScorpioSession::finalize called, but a decomp is still stored elsewhere.\n" + " - decomp name: " + it.first + "\n"); + + int err = PIOc_freedecomp(s.pio_sysid,it.second->ncid); + check_scorpio_noerr(err,"finalize_subsystem","freedecomp"); } + s.decomps.clear(); - return true; +#ifndef SCREAM_CIME_BUILD + // Don't finalize in CIME builds, since the coupler will take care of it + PIOc_finalize (s.pio_sysid); +#endif + + s.pio_sysid = -1; + s.pio_type_default = -1; + s.pio_format = -1; + s.pio_rearranger = -1; } -/* ----------------------------------------------------------------- */ -bool has_variable (const std::string& filename, const std::string& varname) + +// ========================= File operations ===================== // + +void register_file (const std::string& filename, + const FileMode mode, + const IOType iotype) { - int ncid, varid, err; + auto& s = ScorpioSession::instance(); + auto& f = s.files[filename]; + EKAT_REQUIRE_MSG (f.mode==Unset || f.mode==mode, + "Error! File was already opened with a different mode.\n" + " - filename: " + filename + "\n" + " - old mode: " + e2str(f.mode) + "\n" + " - new mode: " + e2str(mode) + "\n"); + EKAT_REQUIRE_MSG (f.mode==Unset || f.iotype==iotype, + "Error! File was already opened with a different iotype.\n" + " - filename: " + filename + "\n" + " - old type: " + iotype2str(f.iotype) + "\n" + " - new type: " + iotype2str(iotype) + "\n"); + + if (f.mode == Unset) { + // First time we ask for this file. Call PIO open routine(s) + int err; + int iotype_int = iotype==IOType::DefaultIOType + ? s.pio_type_default + : static_cast(iotype); + if (mode & Read) { + auto write = mode & Write ? PIO_WRITE : PIO_NOWRITE; + err = PIOc_openfile(s.pio_sysid,&f.ncid,&iotype_int,filename.c_str(),write); + f.enddef = true; + } else { + err = PIOc_createfile(s.pio_sysid,&f.ncid,&iotype_int,filename.c_str(),s.pio_format); + f.enddef = false; + } + + EKAT_REQUIRE_MSG (err==PIO_NOERR, + "Error! Something went wrong while opening a file.\n" + " - filename : " + filename + "\n" + " - file mode: " + e2str(mode) + "\n" + " - pio error: " + std::to_string(err) + "\n"); + + f.mode = mode; + f.iotype = iotype; + f.name = filename; + + if (mode & Read) { + // Read all dims/vars from file + PIO_Offset len; + int ndims, nvars, ngatts, unlimdimid; + err = PIOc_inq(f.ncid, &ndims, &nvars, &ngatts, &unlimdimid); + check_scorpio_noerr(err,f.name,"register_file","inq"); + + char name[PIO_MAX_NAME]; + for (int idim=0; idim(); + dim->name = name; + dim->fid = f.ncid; + dim->length = len; + dim->ncid = idim; + dim->unlimited = idim==unlimdimid; + + if (dim->unlimited) { + f.time_dim = dim; + } + } + + auto find_dim = [&](int dimid) -> std::shared_ptr { + std::shared_ptr d; + for (auto it : f.dims) { + if (it.second->ncid==dimid) { + d = it.second; + } + } + EKAT_REQUIRE_MSG (d!=nullptr, + "Error! Could not locat dimension id in the file.\n" + " - filename: " + f.name + "\n" + " - dim id : " + std::to_string(dimid) + "\n"); + return d; + }; + int dtype,natts; + int dimids[PIO_MAX_DIMS]; + for (int ivar=0; ivar(); + var->name = name; + var->ncid = ivar; + var->fid = f.ncid; + for (int idim=0; idimunlimited) { + var->time_dep = true; + var->num_records = dim->length; + } else { + var->dims.push_back(find_dim(dimids[idim])); + } + } - bool was_open = is_file_open_c2f(filename.c_str(),-1); - if (not was_open) { - register_file(filename,Read); + for (const auto& dt : {"int","int64","float","double","char"}) { + if (nctype(dt)==dtype) { + var->dtype = var->nc_dtype = dt; + break; + } + } + EKAT_REQUIRE_MSG (var->dtype!="", + "Error! Variable data type not supported.\n" + " - filename: " + filename + "\n" + " - varname : " + var->name + "\n" + " - nc dtype: " + std::to_string(dtype) + "\n"); + + for (int iatt=0; iattunits = name; + break; + } + } + } + } } + ++f.num_customers; +} - ncid = get_file_ncid_c2f (filename.c_str()); - err = PIOc_inq_varid(ncid,varname.c_str(),&varid); - if (err==PIO_ENOTVAR) { - return false; +void release_file (const std::string& filename) +{ + auto& f = impl::get_file(filename,"scorpio::release_file"); + + --f.num_customers; + if (f.num_customers>0) { + return; } - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while retrieving variable id.\n" - " - filename : " + filename + "\n" - " - varname : " + varname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - if (not was_open) { - eam_pio_closefile(filename); + + int err; + if (f.mode & Write) { + err = PIOc_sync(f.ncid); + check_scorpio_noerr (err,f.name,"release_file","sync"); } - return true; + err = PIOc_closefile(f.ncid); + check_scorpio_noerr (err,f.name,"release_file","closefile"); + + auto& s = ScorpioSession::instance(); + s.files.erase(filename); } -bool has_attribute (const std::string& filename, const std::string& attname) +void flush_file (const std::string &filename) { - return has_attribute(filename,"GLOBAL",attname); + auto& f = impl::get_file(filename,"scorpio::sync_file"); + + EKAT_REQUIRE_MSG (f.mode & Write, + "Error! Cannot call sync_file. File is read-only.\n" + " - filename: " + filename + "\n"); + + int err = PIOc_sync(f.ncid); + check_scorpio_noerr (err,f.name,"sync_file","sync"); } -bool has_attribute (const std::string& filename, const std::string& varname, const std::string& attname) +void redef(const std::string &filename) { - int ncid, varid, attid, err; + auto& f = impl::get_file(filename,"scorpio::redef"); + + EKAT_REQUIRE_MSG (f.mode & Write, + "Error! Could not call redef on the input file. File is read-only.\n" + " - filename: " + filename + "\n"); - bool was_open = is_file_open_c2f(filename.c_str(),-1); - if (not was_open) { - register_file(filename,Read); + if (f.enddef) { + int err = PIOc_redef(f.ncid); + check_scorpio_noerr (err,f.name,"redef","redef"); + f.enddef = false; } +} - // Get file id - ncid = get_file_ncid_c2f (filename.c_str()); +void enddef(const std::string &filename) +{ + auto& f = impl::get_file(filename,"scorpio::enddef"); - // Get var id - if (varname=="GLOBAL") { - varid = PIO_GLOBAL; + if (not f.enddef) { + int err = PIOc_enddef(f.ncid); + check_scorpio_noerr (err,f.name,"enddef","enddef"); + f.enddef = true; + } +} + +bool is_file_open (const std::string& filename, const FileMode mode) +{ + auto& s = ScorpioSession::instance(); + auto it = s.files.find(filename); + if (it==s.files.end()) return false; + + return mode==Unset || (mode & it->second.mode); +} + +// =================== Dimensions operations ======================= // + +void define_dim (const std::string& filename, const std::string& dimname, const int length) +{ + auto& f = impl::get_file(filename,"scorpio::define_dim"); + + EKAT_REQUIRE_MSG (f.mode & Write, + "Error! Could not define dimension. File is read-only.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + + auto& dim = f.dims[dimname]; + + bool unlimited = length==0; + + if (dim==nullptr) { + EKAT_REQUIRE_MSG (f.mode!=Append, + "Error! Cannot add a new dim when the file is open in append mode.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + // Create new dimension + dim = std::make_shared(); + dim->name = dimname; + dim->fid = f.ncid; + dim->length = length; + dim->unlimited = unlimited; + + // Define the dimension in PIO + int err = PIOc_def_dim(f.ncid,dimname.c_str(),dim->length,&dim->ncid); + check_scorpio_noerr (err,f.name,"dimension",dimname,"define_dim","def_dim"); } else { - err = PIOc_inq_varid(ncid,varname.c_str(),&varid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while retrieving variable id.\n" - " - filename : " + filename + "\n" - " - varname : " + varname + "\n" - " - pio error: " + std::to_string(err) + "\n"); + // Already defined. Check that the dim specs are the same. + EKAT_REQUIRE_MSG (unlimited==dim->unlimited, + "Error! Redefining dimension with different unlimited flag.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n" + " - old unlimited:" + (dim->unlimited ? "yes" : "no" )+ "\n" + " - new unlimited:" + (unlimited ? "yes" : "no") + "\n"); + + EKAT_REQUIRE_MSG (unlimited || length==dim->length, + "Error! Redefining dimension with a different (local) length.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n" + " - old length:" + std::to_string(dim->length)+ "\n" + " - new length:" + std::to_string(length) + "\n"); } +} - // Get att id - err = PIOc_inq_attid(ncid,varid,attname.c_str(),&attid); - if (err==PIO_ENOTATT) { +bool has_dim (const std::string& filename, + const std::string& dimname, + const int length) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + auto it = pf.file->dims.find(dimname); + if (it==pf.file->dims.end()) { return false; + } else if (length==0) { + return it->second->unlimited; + } else if (length>0) { + return it->second->length==length; } - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while retrieving attribute id.\n" - " - filename : " + filename + "\n" - " - varname : " + varname + "\n" - " - attname : " + attname + "\n" - " - pio error: " + std::to_string(err) + "\n"); + return true; +} - if (not was_open) { - eam_pio_closefile(filename); - } +int get_dimlen (const std::string& filename, const std::string& dimname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); - return true; + EKAT_REQUIRE_MSG (has_dim(filename,dimname), + "Error! Could not inquire dimension length. The dimension is not in the file.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + + return pf.file->dims.at(dimname)->length; } -/* ----------------------------------------------------------------- */ -void set_dof(const std::string& filename, const std::string& varname, const Int dof_len, const std::int64_t* x_dof) { - set_dof_c2f(filename.c_str(),varname.c_str(),dof_len,x_dof); +int get_dimlen_local (const std::string& filename, const std::string& dimname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + EKAT_REQUIRE_MSG (has_dim(filename,dimname), + "Error! Could not inquire dimension local length. The dimension is not in the file.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + + const auto& dim = pf.file->dims.at(dimname); + return dim->offsets==nullptr ? dim->length : dim->offsets->size(); } -/* ----------------------------------------------------------------- */ -void pio_update_time(const std::string& filename, const double time) { - pio_update_time_c2f(filename.c_str(),time); +bool is_dim_unlimited (const std::string& filename, + const std::string& dimname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + EKAT_REQUIRE_MSG (has_dim(filename,dimname), + "Error! Could not inquire if dimension is unlimited. The dimension is not in the file.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + + return pf.file->dims.at(dimname)->unlimited; } -/* ----------------------------------------------------------------- */ -void register_dimension(const std::string &filename, const std::string& shortname, const std::string& longname, const int length, const bool partitioned) + +int get_time_len (const std::string& filename) { - int mode = get_file_mode_c2f(filename.c_str()); - std::string mode_str = mode==Read ? "Read" : (mode==Write ? "Write" : "Append"); - if (mode!=Write) { - // Ensure the dimension already exists, and that it has the correct size (if not unlimited) - int ncid,dimid,unlimid,err; + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); - ncid = get_file_ncid_c2f (filename.c_str()); - err = PIOc_inq_dimid(ncid,shortname.c_str(),&dimid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Could not retrieve dimension id from file open in " + mode_str + " mode.\n" - " - filename: " + filename + "\n" - " - dimension : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); + EKAT_REQUIRE_MSG (pf.file->time_dim!=nullptr, + "Error! Could not inquire time dimension length. The time dimension is not in the file.\n" + " - filename: " + filename + "\n"); - err = PIOc_inq_unlimdim(ncid,&unlimid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong querying for the unlimited dimension id.\n" + return pf.file->time_dim->length; +} + +std::string get_time_name (const std::string& filename) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + EKAT_REQUIRE_MSG (pf.file->time_dim!=nullptr, + "Error! Could not inquire time dimension name. The time dimension is not in the file.\n" + " - filename: " + filename + "\n"); + + return pf.file->time_dim->name; +} + +// =================== Decompositions operations ==================== // + +// NOTES: +// - this is a local function, we don't expose it. It's only called inside other scorpio utilities +// - we don't really *need* filename, it's only to print more context in case of errors +void set_var_decomp (PIOVar& var, + const std::string& filename) +{ + for (size_t i=1; ioffsets==nullptr, + "Error! We currently only allow decomposition on slowest-striding dimension.\n" + " Generalizing is not complicated, but it was not a priority.\n" " - filename: " + filename + "\n" - " - dimension : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - if (length==0) { - EKAT_REQUIRE_MSG ( unlimid==dimid, - "Error! Input dimension is unlimited, but does not appear to be unlimited in the file (open in " + mode_str + " mode).\n" - " - filename: " + filename + "\n" - " - dimension : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - } else { - EKAT_REQUIRE_MSG ( unlimid!=dimid, - "Error! Input dimension is not unlimited, but it appears to be unlimited in the file (open in " + mode_str + " mode).\n" - " - filename: " + filename + "\n" - " - dimension : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); + " - varname : " + var.name + "\n" + " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n" + " - bad dim : " + var.dims[i]->name + "\n"); + } + EKAT_REQUIRE_MSG (var.dims[0]->offsets!=nullptr, + "Error! Calling set_var_decomp, but the var first dimension does not appear to be decomposed.\n" + " - filename: " + filename + "\n" + " - varname : " + var.name + "\n" + " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n"); + EKAT_REQUIRE_MSG (var.decomp==nullptr, + "Error! You should have invalidated var.decomp before attempting to reset it.\n" + " - filename : " + filename + "\n" + " - varname : " + var.name + "\n" + " - var decomp: " + var.decomp->name + "\n"); - int len_from_file = get_dimlen(filename,shortname); - EKAT_REQUIRE_MSG (length==len_from_file, - "Error! Input dimension length does not match the one from the file (open in " + mode_str + " mode).\n" - " - filename: " + filename + "\n" - " - dimension : " + shortname + "\n" - " - input dim length: " + std::to_string(length) + "\n" - " - file dim length : " + std::to_string(len_from_file) + "\n"); + // Create decomp name: dtype-dim1_dim2_..._dimk + std::shared_ptr decomp_dim; + std::string decomp_tag = var.dtype + "-"; + for (auto d : var.dims) { + decomp_tag += d->name + "<" + std::to_string(d->length) + ">_"; + } + decomp_tag.pop_back(); // remove trailing underscore + + // Check if a decomp with this name already exists + auto& s = ScorpioSession::instance(); + auto& decomp = s.decomps[decomp_tag]; +#ifndef NDEBUG + // Extra check: all ranks must agree on whether they have the decomposition! + // If they don't agree, some rank will be stuck in a PIO call, waiting for others + int found = decomp==nullptr ? 0 : 1; + int min_found, max_found; + const auto& comm = ScorpioSession::instance().comm; + comm.all_reduce(&found,&min_found,1,MPI_MIN); + comm.all_reduce(&found,&max_found,1,MPI_MAX); + EKAT_REQUIRE_MSG(min_found==max_found, + "Error! Decomposition already present on some ranks but not all.\n" + " - filename: " + filename + "\n" + " - varname : " + var.name + "\n" + " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n" + " - decopm tag: " + decomp_tag + "\n"); +#endif + + if (decomp==nullptr) { + // We haven't create this decomp yet. Go ahead and create one + decomp = std::make_shared(); + decomp->name = decomp_tag; + decomp->dim = var.dims[0]; + + int ndims = var.dims.size(); + + // Get ALL dims global lengths, and compute prod of *non-decomposed* dims + std::vector gdimlen = {decomp->dim->length}; + int non_decomp_dim_prod = 1; + for (int idim=1; idimlength); + non_decomp_dim_prod *= d->length; + } + + // Create offsets list + const auto& dim_offsets = *decomp->dim->offsets; + int dim_loc_len = dim_offsets.size(); + decomp->offsets.resize (non_decomp_dim_prod*dim_loc_len); + for (int idof=0; idofoffsets.begin()+ idof*non_decomp_dim_prod; + auto end = beg + non_decomp_dim_prod; + std::iota (beg,end,non_decomp_dim_prod*dof_offset); } + + // Create PIO decomp + int maplen = decomp->offsets.size(); + PIO_Offset* compmap = reinterpret_cast(decomp->offsets.data()); + int err = PIOc_init_decomp(s.pio_sysid,nctype(var.dtype),ndims,gdimlen.data(), + maplen,compmap, &decomp->ncid,s.pio_rearranger, + nullptr,nullptr); + + check_scorpio_noerr(err,filename,"decomp",decomp_tag,"set_var_decomp","InitDecomp"); } - register_dimension_c2f(filename.c_str(), shortname.c_str(), longname.c_str(), length, partitioned); + // Set decomp data in the var + var.decomp = decomp; } -/* ----------------------------------------------------------------- */ -void register_variable(const std::string& filename, const std::string& shortname, const std::string& longname, - const std::vector& var_dimensions, - const std::string& dtype, const std::string& pio_decomp_tag) + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const std::vector& my_offsets, + const bool allow_reset) { - // This overload does not require to specify an nc data type, so it *MUST* be used when the - // file access mode is either Read or Append. Either way, a) the var should be on file already, - // and b) so should be the dimensions - EKAT_REQUIRE_MSG (has_variable(filename,shortname), - "Error! This overload of register_variable *assumes* the variable is already in the file, but wasn't found.\n" + auto& s = ScorpioSession::instance(); + auto& f = impl::get_file(filename,"scorpio::set_decomp"); + auto& dim = impl::get_dim(filename,dimname,"scorpio::set_dim_decomp"); + + EKAT_REQUIRE_MSG (not dim.unlimited, + "Error! Cannot partition an unlimited dimension.\n" " - filename: " + filename + "\n" - " - varname : " + shortname + "\n"); - for (const auto& dimname : var_dimensions) { - int len = get_dimlen (filename,dimname); - // WARNING! If the dimension was not yet registered, it will be registered as a NOT partitioned dim. - // If this dim should be partitioned, then register it *before* the variable - register_dimension(filename,dimname,dimname,len,false); - } - register_variable(filename,shortname,longname,"",var_dimensions,dtype,"",pio_decomp_tag); -} -void register_variable(const std::string &filename, const std::string& shortname, const std::string& longname, - const std::string& units_in, const std::vector& var_dimensions, - const std::string& dtype, const std::string& nc_dtype_in, const std::string& pio_decomp_tag) -{ - // Local copies, since we can modify them in case of defaults - auto units = units_in; - auto nc_dtype = nc_dtype_in; - - int mode = get_file_mode_c2f(filename.c_str()); - std::string mode_str = mode==Read ? "Read" : (mode==Write ? "Write" : "Append"); - - bool has_var = has_variable(filename,shortname); - if (mode==Write) { - EKAT_REQUIRE_MSG ( units!="" and nc_dtype!="", - "Error! Missing valid units and/or nc_dtype arguments for file open in Write mode.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n"); - } else { - EKAT_REQUIRE_MSG ( has_var, - "Error! Variable not found in file open in " + mode_str + " mode.\n" + " - dimname : " + dimname + "\n"); + + if (dim.offsets!=nullptr) { + if (allow_reset) { + // We likely won't need the previously created decomps that included this dimension. + // So, as we remove decomps from vars that have this dim, keep track of their name, + // so that we can free them later *if no other users of them remain*. + std::set decomps_to_remove; + for (auto it : f.vars) { + auto v = it.second; + if (v->decomp!=nullptr and v->decomp->dim->name==dimname) { + decomps_to_remove.insert(v->decomp->name); + v->decomp = nullptr; + } + } + for (const auto& dn : decomps_to_remove) { + if (s.decomps.at(dn).use_count()==1) { + auto decomp = s.decomps.at(dn); + // There is no other customer of this decomposition, so we can safely free it + int err = PIOc_freedecomp(s.pio_sysid,decomp->ncid); + check_scorpio_noerr(err,filename,"decomp",dn,"set_dim_decomp","freedecomp"); + s.decomps.erase(dn); + } + } + } else { + // Check that the offsets are (globally) the same + int same = *dim.offsets==my_offsets; + const auto& comm = ScorpioSession::instance().comm; + comm.all_reduce(&same,1,MPI_MIN); + EKAT_REQUIRE_MSG(same==1, + "Error! Attempt to redefine a decomposition with a different dofs distribution.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n" + "If you are attempting to redefine the decomp, call this function with throw_if_changing_decomp=false.\n"); + + // Same decomposition, so we can just return + return; + } + } + + // Check that offsets are less than the global dimension length + for (auto o : my_offsets) { + EKAT_REQUIRE_MSG (o>=0 && o>(my_offsets); - if (has_var) { - // The file already exists or the var was already registered. - // Make sure we're registering the var with the same specs - int ncid = get_file_ncid_c2f (filename.c_str()); - int vid,ndims,err; - err = PIOc_inq_varid(ncid,shortname.c_str(),&vid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong retrieving variable id.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - err = PIOc_inq_varndims(ncid,vid,&ndims); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong inquiring the number of dimensions of a variable.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - std::vector dims(ndims); - err = PIOc_inq_vardimid(ncid,vid,dims.data()); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong inquiring the dimensions ids of a variable.\n" + // If vars were already defined, we need to process them, + // and create the proper PIODecomp objects. + for (auto it : f.vars) { + if (ekat::contains(it.second->dim_names(),dimname)) { + set_var_decomp (*it.second,filename); + } + } +} + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const offset_t start, const offset_t count, + const bool allow_reset) +{ + std::vector offsets(count); + std::iota(offsets.begin(),offsets.end(),start); + set_dim_decomp(filename,dimname,offsets,allow_reset); +} + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const bool allow_reset) +{ + const auto& comm = ScorpioSession::instance().comm; + + const int glen = get_dimlen(filename,dimname); + int len = glen / comm.size(); + if (comm.rank() < (glen % comm.size())) { + ++len; + } + + offset_t offset = len; + comm.scan(&offset,1,MPI_SUM); + offset -= len; // scan is inclusive, but we need exclusive + + set_dim_decomp (filename,dimname,offset,len,allow_reset); +} + +// ================== Variable operations ================== // + +// Define var on output file (cannot call on Read/Append files) +void define_var (const std::string& filename, const std::string& varname, + const std::string& units, const std::vector& dimensions, + const std::string& dtype, const std::string& nc_dtype, + const bool time_dep) +{ + auto& f = impl::get_file(filename,"scorpio::define_var"); + + EKAT_REQUIRE_MSG (f.mode & Write, + "Error! Could not define variable. File is read-only.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n"); + + EKAT_REQUIRE_MSG (not time_dep || f.time_dim!=nullptr, + "Error! Cannot define time-dependent variable: no time dimension defined.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n"); + + if (f.vars.count(varname)==0) { + EKAT_REQUIRE_MSG (f.mode!=Append, + "Error! Cannot add a new var when the file is open in append mode.\n" " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - std::vector dims_from_file(ndims); - for (int i=0; i(); + var->name = varname; + var->fid = f.ncid; + var->units = units; + var->dtype = refine_dtype(dtype); + var->nc_dtype = refine_dtype(nc_dtype); + var->time_dep = time_dep; + int ndims = dimensions.size() + (time_dep ? 1 : 0); + std::vector dimids; + if (time_dep) { + dimids.push_back(f.time_dim->ncid); + } + for (const auto& dname : dimensions) { + EKAT_REQUIRE_MSG (has_dim(filename,dname), + "Error! Cannot create variable. Dimension not found.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - var dims : " + ekat::join(dimensions,",") + "\n" + " - file dims: " + print_map_keys(f.dims) + "\n"); + auto dim = f.dims.at(dname); + var->dims.push_back(dim); + dimids.push_back(dim->ncid); } - // Here, let's only try to access var_dimensions[0] when we know for sure - // that var_dimensions is actually dimensioned (i.e., .size()>0) - if (var_dimensions.size()>0) { - if (mode==Read && (dims_from_file[0]=="time" && var_dimensions[0]!="time")) { - // For Read operations, we may not consider "time" as a field dimension, so if the - // input file has "time", simply disregard it in this check. - dims_from_file.erase(dims_from_file.begin()); - } - } else { - if (mode==Read && (dims_from_file[0]=="time")) { - // For Read operations, we may not consider "time" as a field dimension, so if the - // input file has "time", simply disregard it in this check. - dims_from_file.erase(dims_from_file.begin()); - } + // Define the variable in PIO + int err = PIOc_def_var(f.ncid,varname.c_str(),nctype(nc_dtype),ndims,dimids.data(),&var->ncid); + check_scorpio_noerr(err,f.name,"variable",varname,"define_var","def_var"); + + f.vars[varname] = var; + + if (units!="") { + // Add units attribute + set_attribute(filename,varname,"units",units); } - std::reverse(dims_from_file.begin(),dims_from_file.end()); - EKAT_REQUIRE_MSG(var_dimensions==dims_from_file, - "Error! Input variable dimensions do not match the ones from the file.\n" - " - filename : " + filename + "\n" - " - varname : " + shortname + "\n" - " - input dims: (" + ekat::join(var_dimensions,",") + ")\n" - " - file dims : (" + ekat::join(dims_from_file,",") + ")\n"); - - // Check nc dtype only if user bothered specifying it (if mode=Read, probably the user doesn't care) - nc_type type; - err = PIOc_inq_vartype(ncid,vid,&type); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while inquiring variable data type.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - EKAT_REQUIRE_MSG (nc_dtype=="" or type==nctype(nc_dtype), - "Error! Input NC data type does not match the one from the file.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - input dtype : " + nc_dtype + "\n" - " - file dtype : " + nctype2str(type) + "\n"); - nc_dtype = nctype2str(type); - - // Get var units (if set) - int natts; - err = PIOc_inq_varnatts(ncid,vid,&natts); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "Error! Something went wrong while inquiring a variable's number of attributes.\n" - " - filename: " + filename + "\n" - " - varname : " + shortname + "\n" - " - pio error: " + std::to_string(err) + "\n"); - std::string units_from_file(PIO_MAX_NAME,'\0'); - std::string att_name(PIO_MAX_NAME,'\0'); - for (int i=0; idims.size()>0 and var->dims[0]->offsets!=nullptr) { + set_var_decomp (*var,filename); } + } else { + const auto& var = f.vars.at(varname); + // The variable was already defined. Check that important metadata is the same + EKAT_REQUIRE_MSG (var->units==units, + "Error! Attempt to redefine variable with different units.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - old units: " + var->units + "\n" + " - new units: " + units + "\n"); + EKAT_REQUIRE_MSG (var->dtype==refine_dtype(dtype), + "Error! Attempt to redefine variable with different data type.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - old dtype: " + var->dtype + "\n" + " - new dtype: " + refine_dtype(dtype) + "\n"); + EKAT_REQUIRE_MSG (var->nc_dtype==refine_dtype(nc_dtype), + "Error! Attempt to redefine variable with different PIO data type.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - old pio dtype: " + var->nc_dtype + "\n" + " - new pio dtype: " + refine_dtype(nc_dtype) + "\n"); + EKAT_REQUIRE_MSG (var->time_dep==time_dep, + "Error! Attempt to redefine variable with different time dep flag.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - old time_dep: " + (var->time_dep ? "yes" : "no") + "\n" + " - new time_dep: " + ( time_dep ? "yes" : "no") + "\n"); + const auto var_dims = ekat::join(var->dims,get_entity_name,","); + EKAT_REQUIRE_MSG (var_dims==ekat::join(dimensions,","), + "Error! Attempt to redefine variable with different dimensions.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n" + " - old dims: " + var_dims + "\n" + " - new dims: " + ekat::join(dimensions,",") + "\n"); } +} - // Convert the vector of strings that contains the variable dimensions to a char array - const int numdims = var_dimensions.size(); - std::vector var_dimensions_c(numdims); - for (int ii = 0;ii& dimensions, + const std::string& dtype, + const bool time_dependent) +{ + define_var(filename,varname,"",dimensions,dtype,dtype,time_dependent); } -/* ----------------------------------------------------------------- */ -void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const float meta_val) { - set_variable_metadata_float_c2f(filename.c_str(),varname.c_str(),meta_name.c_str(),meta_val); + +// This overload is not exposed externally. Also, filename is only +// used to print it in case there are errors +void change_var_dtype (PIOVar& var, + const std::string& dtype, + const std::string& filename) +{ + if (refine_dtype(dtype)==refine_dtype(var.dtype)) { + // The type is not changing, nothing to do + return; + } + + var.dtype = refine_dtype(dtype); + if (var.decomp) { + // Re-decompose the variable, with new data type + var.decomp = nullptr; + set_var_decomp (var,filename); + } } -/* ----------------------------------------------------------------- */ -void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const double meta_val) { - set_variable_metadata_double_c2f(filename.c_str(),varname.c_str(),meta_name.c_str(),meta_val); + +void change_var_dtype (const std::string& filename, + const std::string& varname, + const std::string& dtype) +{ + auto& var = impl::get_var(filename,varname,"scorpio::change_var_dtype"); + change_var_dtype(var,dtype,filename); } -/* ----------------------------------------------------------------- */ -void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const std::string& meta_val) { - set_variable_metadata_char_c2f(filename.c_str(),varname.c_str(),meta_name.c_str(),meta_val.c_str()); + +bool has_var (const std::string& filename, const std::string& varname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + return pf.file->vars.count(varname)==1; } -/* ----------------------------------------------------------------- */ -void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, float& meta_val) { - meta_val = get_variable_metadata_float_c2f(filename.c_str(),varname.c_str(),meta_name.c_str()); + +const PIOVar& get_var (const std::string& filename, + const std::string& varname) +{ + return impl::get_var(filename,varname,"scorpio::get_var"); } -/* ----------------------------------------------------------------- */ -void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, double& meta_val) { - meta_val = get_variable_metadata_double_c2f(filename.c_str(),varname.c_str(),meta_name.c_str()); + +void define_time (const std::string& filename, const std::string& units, const std::string& time_name) +{ + auto& f = impl::get_file(filename,"scorpio::define_time"); + EKAT_REQUIRE_MSG (f.time_dim==nullptr, + "Error! Attempt to redeclare unlimited dimension.\n" + " - filename: " + filename + "\n"); + + define_dim(filename,time_name,0); + f.time_dim = f.dims.at(time_name); + + define_var(filename,time_name,units,{},"double","double",true); } -/* ----------------------------------------------------------------- */ -void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, std::string& meta_val) { - meta_val.resize(256); - get_variable_metadata_char_c2f(filename.c_str(),varname.c_str(),meta_name.c_str(),&meta_val[0]); - // If terminating char is not found, meta_val simply uses all 256 chars - if (meta_val.find('\0')!=std::string::npos) { - meta_val.resize(meta_val.find('\0')); +void pretend_dim_is_unlimited (const std::string& filename, const std::string& dimname) +{ + auto& f = impl::get_file(filename,"scorpio::mark_dim_as_time"); + EKAT_REQUIRE_MSG (f.mode==Read, + "Error! Cannot interpret dimension as 'time' dim. File not in Read mode.\n" + " - filename : " + filename + "\n" + " - file mode: " + e2str(f.mode) + "\n"); + + if (f.time_dim==nullptr) { + EKAT_REQUIRE_MSG (has_dim(filename,dimname), + "Error! Cannot interpret dimension as 'time' dim. Dimension not found.\n" + " - filename: " + filename + "\n" + " - dimname : " + dimname + "\n"); + + auto dim = f.dims.at(dimname); + f.time_dim = dim; + + // If a var has "time" in its dims (must be the 1st dim!), + // remove it. Recall that we only store non-time dims in + // the list of var dims. + for (auto& it : f.vars) { + auto& v = it.second; + if (v->dims.size()>0 and v->dims[0]->name==dimname) { + v->dims.erase(v->dims.begin()); + v->size = -1; + v->time_dep = true; + } + } + } else { + EKAT_REQUIRE_MSG (f.time_dim->name==dimname, + "Error! Attempt to change the time dimension.\n" + " - filenama : " + filename + "\n" + " - old time dim: " + f.time_dim->name + "\n" + " - new time dim: " + dimname + "\n"); } } -/* ----------------------------------------------------------------- */ -ekat::any get_any_attribute (const std::string& filename, const std::string& att_name) { - auto out = get_any_attribute(filename,"GLOBAL",att_name); - return out; + +// Update value of time variable, increasing time dim length +void update_time(const std::string &filename, const double time) { + const auto& f = impl::get_file(filename,"scorpio::update_time"); + auto& time_dim = *f.time_dim; + const auto& var = impl::get_var(filename,time_dim.name,"scorpio::update_time"); + + PIO_Offset index = time_dim.length; + int err = PIOc_put_var1(f.ncid,var.ncid,&index,&time); + check_scorpio_noerr (err,f.name,"update time","put_var1"); + ++time_dim.length; } -/* ----------------------------------------------------------------- */ -ekat::any get_any_attribute (const std::string& filename, const std::string& var_name, const std::string& att_name) { - register_file(filename,Read); - auto ncid = get_file_ncid_c2f (filename.c_str()); - EKAT_REQUIRE_MSG (ncid>=0, - "[get_any_attribute] Error! Could not retrieve file ncid.\n" - " - filename : " + filename + "\n"); - int varid; +double get_time (const std::string& filename, const int time_index) +{ + impl::PeekFile pf (filename); + + const auto& time_name = pf.file->time_dim->name; + double t; + read_var(filename,time_name,&t,time_index); + return t; +} + +std::vector get_all_times (const std::string& filename) +{ + impl::PeekFile pf (filename); + const auto& dim = *pf.file->time_dim; + + std::vector times (dim.length); + for (int i=0; i +void read_var (const std::string &filename, const std::string &varname, T* buf, const int time_index) +{ + EKAT_REQUIRE_MSG (buf!=nullptr, + "Error! Cannot read from provided pointer. Invalid buffer pointer.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n"); + + const auto& f = impl::get_file(filename,"scorpio::read_var"); + auto& var = impl::get_var(filename,varname,"scorpio::read_var"); + + // If the input pointer type already matches var.dtype, this is a no-op + change_var_dtype(var,get_dtype(),filename); + int err; - if (var_name=="GLOBAL") { - varid = PIO_GLOBAL; + int frame = -1; + if (var.time_dep) { + frame = time_index>=0 ? time_index : f.time_dim->length-1; + EKAT_REQUIRE_MSG (framelength, + "Error! Time index out of bounds.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n" + " - time idx: " + std::to_string(time_index) + "\n" + " - time len: " + std::to_string(f.time_dim->length)); + err = PIOc_setframe(f.ncid,var.ncid,frame); + check_scorpio_noerr (err,f.name,"variable",varname,"read_var","setframe"); + } else if (time_index>=0) { + // This is a bit of a hacky usage. We want to read a time index of a var, + // but the time dim is NOT unlimited in the input file. Apparently, SCORPIO + // supports this, and some E3SM input files are indeed like this, so we + // need to support it here too, hacky as it may be. + frame = time_index; + + EKAT_REQUIRE_MSG (framelength, + "Error! First dim index out of bounds.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - first dim idx: " + std::to_string(time_index) + "\n" + " - first dim len: " + std::to_string(var.dims[0]->length) + "\n"); + } + + std::string pioc_func; + if (var.decomp) { + // A decomposed variable, requires read_darray + err = PIOc_read_darray(f.ncid,var.ncid,var.decomp->ncid,var.decomp->offsets.size(),buf); + pioc_func = "read_darray"; } else { - err = PIOc_inq_varid(ncid, var_name.c_str(), &varid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[get_any_attribute] Error! Something went wrong while inquiring variable id.\n" - " - filename : " + filename + "\n" - " - variable : " + var_name + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); + // A non-decomposed variable, use PIOc_get_var(a) + + // If nc data type doesn't match the input pointer, we need to use the var internal buffer + void* io_buf = buf; + if (var.dtype!=var.nc_dtype) { + if (var.size==-1) { + var.size = 1; + for (auto d : var.dims) { + var.size *= d->length; + } + var.buf.resize(var.size*dtype_size(var.nc_dtype)); + } + io_buf = var.buf.data(); + } + + if (frame>=0) { + // We need to get the start/count for each dimension + int ndims = var.dims.size(); + std::vector start (ndims+1,0), count(ndims+1); // +1 for time + start[0] = frame; + count[0] = 1; + for (int idim=0; idimlength; + } + err = PIOc_get_vara(f.ncid,var.ncid,start.data(),count.data(),io_buf); + pioc_func = "get_vara"; + } else { + err = PIOc_get_var(f.ncid,var.ncid,io_buf); + pioc_func = "get_var"; + } + + // If we used the var tmp buffer, copy back into the user-provided pointer + if (var.dtype!=var.nc_dtype) { + if (var.nc_dtype=="int") { + copy_data(reinterpret_cast(io_buf),buf,var.size); + } else if (var.nc_dtype=="int64") { + copy_data(reinterpret_cast(io_buf),buf,var.size); + } else if (var.nc_dtype=="float") { + copy_data(reinterpret_cast(io_buf),buf,var.size); + } else if (var.nc_dtype=="double") { + copy_data(reinterpret_cast(io_buf),buf,var.size); + } + } } + check_scorpio_noerr (err,f.name,"variable",varname,"read_var",pioc_func); +} - nc_type type; - PIO_Offset len; - err = PIOc_inq_att(ncid,varid,att_name.c_str(),&type,&len); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[get_any_attribute] Error! Something went wrong while inquiring global attribute.\n" - " - filename : " + filename + "\n" - " - variable : " + var_name + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); +// Write data from user provided buffer into the requested variable +template +void write_var (const std::string &filename, const std::string &varname, const T* buf, const T* fillValue) +{ + EKAT_REQUIRE_MSG (buf!=nullptr, + "Error! Cannot write in provided pointer. Invalid buffer pointer.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n"); - EKAT_REQUIRE_MSG (len==1 || type==PIO_CHAR, - "[get_any_attribute] Error! Only single value attributes allowed.\n" - " - filename : " + filename + "\n" - " - variable : " + var_name + "\n" - " - attribute: " + att_name + "\n" - " - nc type : " << type << "\n" - " - att len : " << len << "\n"); - - ekat::any att; - if (type==PIO_INT) { - int val; - err = PIOc_get_att(ncid,varid,att_name.c_str(),&val); - att.reset(val); - } else if (type==PIO_DOUBLE) { - double val; - err = PIOc_get_att(ncid,varid,att_name.c_str(),&val); - att.reset(val); - } else if (type==PIO_FLOAT) { - float val; - err = PIOc_get_att(ncid,varid,att_name.c_str(),&val); - att.reset(val); - } else if (type==PIO_CHAR) { - std::string val(len,'\0'); - err = PIOc_get_att(ncid,varid,att_name.c_str(),&val[0]); - att.reset(val); + const auto& f = impl::get_file(filename,"scorpio::write_var"); + auto& var = impl::get_var(filename,varname,"scorpio::write_var"); + + // If the input pointer type already matches var.dtype, this is a no-op + change_var_dtype(var,get_dtype(),filename); + + int err; + + if (var.time_dep) { + ++var.num_records; + EKAT_REQUIRE_MSG (var.num_records==f.time_dim->length, + "Error! Number of records for variable does not match time length.\n" + " - filename: " + filename + "\n" + " - varname : " + varname + "\n" + " - time len: " + std::to_string(f.time_dim->length) + "\n" + " - nrecords: " + std::to_string(var.num_records) + "\n"); + err = PIOc_setframe (f.ncid,var.ncid,var.num_records-1); + check_scorpio_noerr (err,f.name,"variable",varname,"write_var","setframe"); + } + + std::string pioc_func; + if (var.decomp) { + // A decomposed variable, requires write_darray + err = PIOc_write_darray(f.ncid,var.ncid,var.decomp->ncid,var.decomp->offsets.size(),buf,fillValue); + pioc_func = "write_darray"; } else { - EKAT_ERROR_MSG ("[get_any_attribute] Error! Unsupported/unrecognized nc type.\n" - " - filename : " + filename + "\n" - " - variable : " + var_name + "\n" - " - attribute: " + att_name + "\n" - " - nc type : " << type << "\n"); + // A non-decomposed variable, use PIOc_put_var(a) + // If nc data type doesn't match the input pointer, we need to use the var internal buffer + const void* io_buf = buf; + if (var.dtype!=var.nc_dtype) { + if (var.size==-1) { + var.size = 1; + for (auto d : var.dims) { + var.size *= d->length; + } + var.buf.resize(var.size*dtype_size(var.nc_dtype)); + } + io_buf = var.buf.data(); + void* var_buf = var.buf.data(); + if (var.nc_dtype=="int") { + copy_data(buf,reinterpret_cast(var_buf),var.size); + } else if (var.nc_dtype=="int64") { + copy_data(buf,reinterpret_cast(var_buf),var.size); + } else if (var.nc_dtype=="float") { + copy_data(buf,reinterpret_cast(var_buf),var.size); + } else if (var.nc_dtype=="double") { + copy_data(buf,reinterpret_cast(var_buf),var.size); + } + } + + if (var.time_dep) { + // We need to get the start/count for each dimension + int ndims = var.dims.size(); + std::vector start (ndims+1,0), count(ndims+1); + start[0] = f.time_dim->length-1; + count[0] = 1; + for (int idim=0; idimlength; + } + err = PIOc_put_vara(f.ncid,var.ncid,start.data(),count.data(),io_buf); + pioc_func = "put_vara"; + } else { + // Easy: just pass the buffer, and write all entries + err = PIOc_put_var(f.ncid,var.ncid,io_buf); + pioc_func = "put_var"; + } } - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[get_any_attribute] Error! Something went wrong while inquiring global attribute.\n" - " - filename : " + filename + "\n" - " - variable : " + var_name + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); + check_scorpio_noerr (err,f.name,"variable",varname,"write_var",pioc_func); +} + +// ========================== READ/WRITE ETI ========================== // - eam_pio_closefile(filename); - return att; +template void read_var (const std::string&, const std::string&, int*, const int); +template void read_var (const std::string&, const std::string&, long long*, const int); +template void read_var (const std::string&, const std::string&, float*, const int); +template void read_var (const std::string&, const std::string&, double*, const int); +template void read_var (const std::string&, const std::string&, char*, const int); + +template void write_var (const std::string&, const std::string&, const int*, const int*); +template void write_var (const std::string&, const std::string&, const long long*, const long long*); +template void write_var (const std::string&, const std::string&, const float*, const float*); +template void write_var (const std::string&, const std::string&, const double*, const double*); +template void write_var (const std::string&, const std::string&, const char*, const char*); + +// =============== Attributes operations ================== // + +bool has_global_attribute (const std::string& filename, const std::string& attname) +{ + return has_attribute(filename,"GLOBAL",attname); } -void set_any_attribute (const std::string& filename, const std::string& att_name, const ekat::any& att) { - auto ncid = get_file_ncid_c2f (filename.c_str()); - int err; - EKAT_REQUIRE_MSG (ncid>=0, - "[set_any_attribute] Error! Could not retrieve file ncid.\n" - " - filename : " + filename + "\n"); +bool has_attribute (const std::string& filename, const std::string& varname, const std::string& attname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + const int ncid = pf.file->ncid; - bool redef = is_enddef_c2f(filename.c_str()); - if (redef) { - err = PIOc_redef(ncid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[set_any_attribute] Error! Something went wrong while re-opening def phase.\n" - " - filename : " + filename + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); - } - - int varid = PIO_GLOBAL; - if (att.isType()) { - const int& data = ekat::any_cast(att); - err = PIOc_put_att(ncid,varid,att_name.c_str(),PIO_INT,1,&data); - } else if (att.isType()) { - const double& data = ekat::any_cast(att); - err = PIOc_put_att(ncid,varid,att_name.c_str(),PIO_DOUBLE,1,&data); - } else if (att.isType()) { - const float& data = ekat::any_cast(att); - err = PIOc_put_att(ncid,varid,att_name.c_str(),PIO_FLOAT,1,&data); - } else if (att.isType()) { - const std::string& data = ekat::any_cast(att); - err = PIOc_put_att(ncid,varid,att_name.c_str(),PIO_CHAR,data.size(),data.data()); + // Get var id + int varid; + if (varname=="GLOBAL") { + varid = PIO_GLOBAL; } else { - EKAT_ERROR_MSG ("[set_any_attribute] Error! Unsupported/unrecognized att type.\n" - " - filename : " + filename + "\n" - " - att name : " + att_name + "\n" - " - att value: " << att << "\n" - " - type info: " << att.content().type().name() << "\n"); + const auto& var = impl::get_var(filename,varname,"scorpio::has_attribute"); + varid = var.ncid; } + // Get att id + int attid; + int err = PIOc_inq_attid(ncid,varid,attname.c_str(),&attid); + if (err==PIO_ENOTATT) { + return false; + } EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[set_any_attribute] Error! Something went wrong while setting global attribute.\n" - " - filename : " + filename + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); + "Error! Something went wrong while retrieving attribute id.\n" + " - filename : " + filename + "\n" + " - varname : " + varname + "\n" + " - attname : " + attname + "\n" + " - pio error: " + std::to_string(err) + "\n"); - if (redef) { - err = PIOc_enddef(ncid); - EKAT_REQUIRE_MSG (err==PIO_NOERR, - "[set_any_attribute] Error! Something went wrong while re-closing def phase.\n" - " - filename : " + filename + "\n" - " - attribute: " + att_name + "\n" - " - pio error: " << err << "\n"); - } -} -/* ----------------------------------------------------------------- */ -void eam_pio_enddef(const std::string &filename) { - eam_pio_enddef_c2f(filename.c_str()); -} -/* ----------------------------------------------------------------- */ -void eam_pio_redef(const std::string &filename) { - eam_pio_redef_c2f(filename.c_str()); -} -/* ----------------------------------------------------------------- */ -template<> -void grid_read_data_array(const std::string &filename, const std::string &varname, - const int time_index, int *hbuf, const int buf_size) { - grid_read_data_array_c2f_int(filename.c_str(),varname.c_str(),time_index,hbuf,buf_size); -} -template<> -void grid_read_data_array(const std::string &filename, const std::string &varname, - const int time_index, float *hbuf, const int buf_size) { - grid_read_data_array_c2f_float(filename.c_str(),varname.c_str(),time_index,hbuf,buf_size); -} -template<> -void grid_read_data_array(const std::string &filename, const std::string &varname, - const int time_index, double *hbuf, const int buf_size) { - grid_read_data_array_c2f_double(filename.c_str(),varname.c_str(),time_index,hbuf,buf_size); -} -/* ----------------------------------------------------------------- */ -template<> -void grid_write_data_array(const std::string &filename, const std::string &varname, const int* hbuf, const int buf_size) { - grid_write_data_array_c2f_int(filename.c_str(),varname.c_str(),hbuf,buf_size); + return true; } -template<> -void grid_write_data_array(const std::string &filename, const std::string &varname, const float* hbuf, const int buf_size) { - grid_write_data_array_c2f_float(filename.c_str(),varname.c_str(),hbuf,buf_size); + +template +T get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname) +{ + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + int varid; + if (varname=="GLOBAL") { + varid = PIO_GLOBAL; + } else { + varid = impl::get_var(filename,varname,"scorpio::set_any_attribute").ncid; + } + + T val; + int err = PIOc_get_att(pf.file->ncid,varid,attname.c_str(),reinterpret_cast(&val)); + check_scorpio_noerr(err,filename,"attribute",attname,"set_attribute","put_att"); + + return val; } + +// Explicit instantiation +template int get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); +template std::int64_t get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); +template float get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); +template double get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); + +// Full specialization for strings template<> -void grid_write_data_array(const std::string &filename, const std::string &varname, const double* hbuf, const int buf_size) { - grid_write_data_array_c2f_double(filename.c_str(),varname.c_str(),hbuf,buf_size); -} -/* ----------------------------------------------------------------- */ -void write_timestamp (const std::string& filename, const std::string& ts_name, - const util::TimeStamp& ts, const bool write_nsteps) +std::string get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname) { - set_attribute(filename,ts_name,ts.to_string()); - if (write_nsteps) { - set_attribute(filename,ts_name+"_nsteps",ts.get_num_steps()); + // If file wasn't open, open it on the fly. See comment in PeekFile class above. + impl::PeekFile pf(filename); + + int varid; + if (varname=="GLOBAL") { + varid = PIO_GLOBAL; + } else { + varid = impl::get_var(filename,varname,"scorpio::set_any_attribute").ncid; } + + int err; + PIO_Offset len; + err = PIOc_inq_attlen(pf.file->ncid,varid,attname.c_str(),&len); + check_scorpio_noerr(err,filename,"attribute",attname,"set_attribute","inq_attlen"); + + std::string val(len,'\0'); + + err = PIOc_get_att(pf.file->ncid,varid,attname.c_str(),val.data()); + check_scorpio_noerr(err,filename,"attribute",attname,"set_attribute","put_att"); + + return val; } -/* ----------------------------------------------------------------- */ -util::TimeStamp read_timestamp (const std::string& filename, - const std::string& ts_name, - const bool read_nsteps) + +template +void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const T& att) { - auto ts = util::str_to_time_stamp(get_attribute(filename,ts_name)); - if (read_nsteps and has_attribute(filename,ts_name+"_nsteps")) { - ts.set_num_steps(get_attribute(filename,ts_name+"_nsteps")); + const auto& f = impl::get_file (filename,"scorpio::set_any_attribute"); + + int varid; + if (varname=="GLOBAL") { + varid = PIO_GLOBAL; + } else { + varid = impl::get_var(filename,varname,"scorpio::set_any_attribute").ncid; + } + + // If the file was not in define mode, we must call enddef at the end + const bool needs_redef = f.enddef; + if (needs_redef) { + redef(filename); + } + + int err = PIOc_put_att(f.ncid,varid,attname.c_str(),nctype(),nclen(att),ncdata(att)); + check_scorpio_noerr(err,filename,"attribute",attname,"set_attribute","put_att"); + + if (needs_redef) { + enddef(filename); } - return ts; } -/* ----------------------------------------------------------------- */ + +// Explicit instantiation +template void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const int& att); +template void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const std::int64_t& att); +template void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const float& att); +template void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const double& att); +template void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const std::string& att); + } // namespace scorpio } // namespace scream diff --git a/components/eamxx/src/share/io/scream_scorpio_interface.hpp b/components/eamxx/src/share/io/scream_scorpio_interface.hpp index c5ae69ea6c6..549de680fe4 100644 --- a/components/eamxx/src/share/io/scream_scorpio_interface.hpp +++ b/components/eamxx/src/share/io/scream_scorpio_interface.hpp @@ -1,168 +1,240 @@ #ifndef SCREAM_SCORPIO_INTERFACE_HPP #define SCREAM_SCORPIO_INTERFACE_HPP -#include "share/field/field_tag.hpp" -#include "share/scream_types.hpp" -#include "share/util/scream_time_stamp.hpp" +#include "scream_scorpio_types.hpp" -#include "ekat/mpi/ekat_comm.hpp" -#include "ekat/util/ekat_string_utils.hpp" +#include +#include +#include #include -/* C++/F90 bridge to F90 SCORPIO routines */ +/* + * This file contains interfaces to scorpio C library routines + * + * There are two reasons for these interfaces: + * + * - allow better context when exceptions/errors occour: throwing exceptions + * can even allow host code to use try-catch blocks to figure out what works. + * Moreover, the user does not need to continuously check return codes, since + * we throw any time scorpio returns something different from PIO_NOERR. + * - allow using simpler interfaces: by using string/vector and templates, + * the host code can have more compact interfaces. + * + * Most of the "get" interfaces can open a file in read mode on the fly. + * This allows easier usage from the user point of view, but has some drawbacks: + * - the file is open/closed every time you query it + * - the file is open with default IOType + * If you need to do several queries, you should consider registering the file manually + * before doing any query, and release it once you are done. + */ namespace scream { namespace scorpio { - using offset_t = std::int64_t; - - // WARNING: these values must match the ones of file_purpose_in and file_purpose_out - // in the scream_scorpio_interface F90 module - enum FileMode { - Read = 1, - Append = 2, - Write = 4 - }; - - // I/O types supported - enum IOType { - // Default I/O type is used to let the code choose I/O type as needed (via CIME) - DefaultIOType = 0, - NetCDF, - PnetCDF, - Adios, - Hdf5 - }; - - inline int str2iotype(const std::string &str) - { - if(str == "default"){ - return static_cast(IOType::DefaultIOType); - } - else if(str == "netcdf"){ - return static_cast(IOType::NetCDF); - } - else if(str == "pnetcdf"){ - return static_cast(IOType::PnetCDF); - } - else if(str == "adios"){ - return static_cast(IOType::Adios); - } - else if(str == "hdf5"){ - return static_cast(IOType::Hdf5); - } - else{ - return static_cast(IOType::DefaultIOType); - } +inline std::string default_time_name () { return "time"; } + +// Handles aliases for same type: +// single -> float +// float -> float +// double -> double +// real -> float or double (depending on SCREAM_DOUBLE_PRECISION) +std::string refine_dtype (const std::string& dtype); + +template +std::string get_dtype () { + using raw_t = typename std::remove_cv::type; + std::string s; + if constexpr (std::is_same::value) { + s = "int"; + } else if constexpr (std::is_same::value) { + s = "float"; + } else if constexpr (std::is_same::value) { + s = "double"; + } else if constexpr (std::is_integral::value && + std::is_signed::value && + sizeof(raw_t)==sizeof(long long)) { + s = "int64"; + } else if constexpr (std::is_same::value) { + s = "char"; + } else { + EKAT_ERROR_MSG ("Error! Invalid/unsupported data type.\n"); } - - inline std::string iotype2str(int iotype) - { - switch(iotype){ - case static_cast(IOType::DefaultIOType): return "default"; - case static_cast(IOType::NetCDF): return "netcdf"; - case static_cast(IOType::PnetCDF): return "pnetcdf"; - case static_cast(IOType::Adios): return "adios"; - case static_cast(IOType::Hdf5): return "hdf5"; - default: return "default"; - } - } - - /* All scorpio usage requires that the pio_subsystem is initialized. Happens only once per simulation */ - void eam_init_pio_subsystem(const ekat::Comm& comm); - void eam_init_pio_subsystem(const int mpicom, const int atm_id = 0); - /* Cleanup scorpio with pio_finalize */ - void eam_pio_finalize(); - /* Close a file currently open in scorpio */ - void eam_pio_closefile(const std::string& filename); - void eam_flush_file(const std::string& filename); - /* Register a new file to be used for input/output with the scorpio module */ - void register_file(const std::string& filename, const FileMode mode, int iotype = IOType::DefaultIOType); - /* Sets the IO decompostion for all variables in a particular filename. Required after all variables have been registered. Called once per file. */ - int get_dimlen(const std::string& filename, const std::string& dimname); - bool has_dim(const std::string& filename, const std::string& dimname); - bool has_variable (const std::string& filename, const std::string& varname); - bool has_attribute (const std::string& filename, const std::string& attname); - bool has_attribute (const std::string& filename, const std::string& varname, const std::string& attname); - void set_decomp(const std::string& filename); - /* Sets the degrees-of-freedom for a particular variable in a particular file. Called once for each variable, for each file. */ - void set_dof(const std::string &filename, const std::string &varname, const Int dof_len, const offset_t* x_dof); - /* Register a dimension coordinate with a file. Called during the file setup. */ - void register_dimension(const std::string& filename,const std::string& shortname, const std::string& longname, const int length, const bool partitioned); - /* Register a variable with a file. Called during the file setup, for an output stream. */ - void register_variable(const std::string& filename, const std::string& shortname, const std::string& longname, - const std::string& units, const std::vector& var_dimensions, - const std::string& dtype, const std::string& nc_dtype, const std::string& pio_decomp_tag); - void register_variable(const std::string& filename, const std::string& shortname, const std::string& longname, - const std::vector& var_dimensions, - const std::string& dtype, const std::string& pio_decomp_tag); - void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const std::string& meta_val); - void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const float meta_val); - void set_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, const double meta_val); - void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, float& meta_val); - void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, double& meta_val); - void get_variable_metadata (const std::string& filename, const std::string& varname, const std::string& meta_name, std::string& meta_val); - /* Register a variable with a file. Called during the file setup, for an input stream. */ - ekat::any get_any_attribute (const std::string& filename, const std::string& att_name); - ekat::any get_any_attribute (const std::string& filename, const std::string& var_name, const std::string& att_name); - void set_any_attribute (const std::string& filename, const std::string& att_name, const ekat::any& att); - /* End the definition phase for a scorpio file. Last thing called after all dimensions, variables, dof's and decomps have been set. Called once per file. - * Mandatory before writing or reading can happend on file. */ - void eam_pio_enddef(const std::string &filename); - void eam_pio_redef(const std::string &filename); - /* Called each timestep to update the timesnap for the last written output. */ - void pio_update_time(const std::string &filename, const double time); - - // Read data for a specific variable from a specific file. To read data that - // isn't associated with a time index, or to read data at the most recent - // time, set time_index to -1. Otherwise use the proper zero-based time index. - template - void grid_read_data_array (const std::string &filename, const std::string &varname, - const int time_index, T* hbuf, const int buf_size); - /* Write data for a specific variable to a specific file. */ - template - void grid_write_data_array(const std::string &filename, const std::string &varname, - const T* hbuf, const int buf_size); - - template - T get_attribute (const std::string& filename, const std::string& att_name) - { - auto att = get_any_attribute(filename,att_name); - return ekat::any_cast(att); - } - - template - void set_attribute (const std::string& filename, const std::string& att_name, const T& att) - { - ekat::any a(att); - set_any_attribute(filename,att_name,a); - } - - // Shortcut to write/read to/from YYYYMMDD/HHMMSS attributes in the NC file - void write_timestamp (const std::string& filename, const std::string& ts_name, - const util::TimeStamp& ts, const bool write_nsteps = false); - util::TimeStamp read_timestamp (const std::string& filename, - const std::string& ts_name, - const bool read_nsteps = false); - -extern "C" { - /* Query whether the pio subsystem is inited or not */ - bool is_eam_pio_subsystem_inited(); - /* Checks if a file is already open, with the given mode */ - int get_file_ncid_c2f(const char*&& filename); - // If mode<0, then simply checks if file is open, regardless of mode - bool is_file_open_c2f(const char*&& filename, const int& mode); - /* Query a netCDF file for the time variable */ - bool is_enddef_c2f(const char*&& filename); - double read_time_at_index_c2f(const char*&& filename, const int& time_index); - double read_curr_time_c2f(const char*&& filename); - /* Query a netCDF file for the metadata associated w/ a variable */ - float get_variable_metadata_float_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name); - double get_variable_metadata_double_c2f (const char*&& filename, const char*&& varname, const char*&& meta_name); -} // extern "C" + return s; +} + +// =================== Global operations ================= // + +void init_subsystem(const ekat::Comm& comm, const int atm_id = 0); +bool is_subsystem_inited (); +void finalize_subsystem (); + +// =================== File operations ================= // + +// Opens a file, returns const handle to it (useful for Read mode, to get dims/vars) +void register_file(const std::string& filename, const FileMode mode, const IOType iotype = IOType::DefaultIOType); + +// Release a file (if in Write mode, sync and close the file); +void release_file (const std::string& filename); + +// Check if file is open. If mode!=Unset, also checks that it's open with given mode +bool is_file_open (const std::string& filename, const FileMode mode = Unset); + +// Force a flush to file (for Write mode only) +void flush_file (const std::string &filename); + +// Reopen/ends the definition phase +void redef (const std::string &filename); +void enddef (const std::string &filename); + +// =================== Dimensions operations ======================= // + +// Define dim on output file (cannot call on Read/Append files) +void define_dim (const std::string& filename, const std::string& dimname, const int length); + +// Check that the given dimension is in the file. If length>0, also check that the length is as expected. +bool has_dim (const std::string& filename, + const std::string& dimname, + const int length = -1); + +int get_dimlen (const std::string& filename, const std::string& dimname); +int get_dimlen_local (const std::string& filename, const std::string& dimname); + +// Checks if the dimension is unlimited +bool is_dim_unlimited (const std::string& filename, + const std::string& dimname); + +// Get len/name of the time dimension (i.e., the unlimited one) +// NOTE: these throw if time dim is not present. Use has_dim to check first. +int get_time_len (const std::string& filename); +std::string get_time_name (const std::string& filename); + +// =================== Decompositions operations ==================== // + +// Create a decomposition along a particular dimension +// Notes: +// - we declare a decomposition along a single dimension. +// - set_dim_decomp requires *offsets* in the global array, not global indices +// (for 0-based indices, they're the same, but for 1-based indices they're not) +// - the second version is a shortcut for contiguous decompositions. Notice that NO +// check is performed to ensure that the decomposition covers the entire dimension, +// or that there are no overlaps (in fact, one *may* need overlap, in certain reads). +// - the third version is a shortcut of the second, where we compute start/count based +// on a linear decomposition of the dimension along all ranks in the IO comm stored +// in the ScorpioInstance. The return value is the local length of the dimension +// - if allow_reset=true, we simply reset the decomposition (if present). +// - if allow_reset=false, if a decomposition for this dim is already set, we error out + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const std::vector& my_offsets, + const bool allow_reset = false); + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const offset_t start, const offset_t count, + const bool allow_reset = false); + +void set_dim_decomp (const std::string& filename, + const std::string& dimname, + const bool allow_reset = false); + +// ================== Variable operations ================== // + +// Define var on output file (cannot call on Read/Append files) +void define_var (const std::string& filename, const std::string& varname, + const std::string& units, const std::vector& dimensions, + const std::string& dtype, const std::string& nc_dtype, + const bool time_dependent = false); + +// Shortcut when units are not used, and dtype==nc_dtype +void define_var (const std::string& filename, const std::string& varname, + const std::vector& dimensions, + const std::string& dtype, + const bool time_dependent = false); + +// This is useful when reading data sets. E.g., if the pio file is storing +// a var as float, but we need to read it as double, we need to call this. +// NOTE: read_var/write_var automatically change the dtype if the input +// pointer type does not match the var dtype. However, changing dtype +// forces a rebuild of the var decomp (if any). Hence, if you know +// the var WILL be read/written as decomposed, you should call this method +// BEFORE calling set_dim_decomp, so that the decomp is built directly +// with the correct data type (PIO decomps depend on var dtype). +void change_var_dtype (const std::string& filename, + const std::string& varname, + const std::string& dtype); + +// Check that the given variable is in the file. +bool has_var (const std::string& filename, const std::string& varname); + +// Allows to easily query var metadata, such as dims, units, data type,... +const PIOVar& get_var (const std::string& filename, + const std::string& varname); + +// Defines both a time dimension and a time variable +void define_time (const std::string& filename, const std::string& units, + const std::string& time_name = default_time_name()); + +// When we read a file, and there is a "time" dimension that is FIXED rather than UNLIMITED, +// we may have some issues with our read/write logics. Hence, we can use mark a dimension +// as if it was the UNLIMITED time dim. +void pretend_dim_is_unlimited (const std::string& filename, const std::string& dimname); + +// Update value of time variable, increasing time dim length +void update_time(const std::string &filename, const double time); + +// Retrieves the time variable value(s) +double get_time (const std::string& filename, const int time_index = -1); +std::vector get_all_times (const std::string& filename); + +// Read variable into user provided buffer. +// If time dim is present, read given time slice (time_index=-1 means "read last record). +// If time dim is not present and time_index>=0, it is interpreted as the index of the +// first dimension (which is not unlimited). +// NOTE: ETI in the cpp file for int, float, double. +template +void read_var (const std::string &filename, const std::string &varname, T* buf, const int time_index = -1); + +// Write data from user provided buffer into the requested variable +// NOTE: ETI in the cpp file for int, float, double. +template +void write_var (const std::string &filename, const std::string &varname, const T* buf, const T* fillValue = nullptr); + +// =============== Attributes operations ================== // + +// To specify GLOBAL attributes, pass "GLOBAL" as varname +// NOTE: set/get_attribute are implemented in the cpp file, +// with Explicit Instantiation only for: +// int, std::int64_t, float, double, std::string + +bool has_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); + +template +T get_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname); + +template +void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const T& att); + +// Shortcut, to allow calling set_attribute with compile-time strings, like so +// set_attribute(my_file,my_var,my_att_name,"my_value"); +template +inline void set_attribute (const std::string& filename, + const std::string& varname, + const std::string& attname, + const char (&att)[N]) +{ + set_attribute(filename,varname,attname,att); +} } // namespace scorpio } // namespace scream -#endif // define SCREAM_SCORPIO_INTERFACE_HPP +#endif // define SCREAM_SCORPIO_INTERFACE_HPP diff --git a/components/eamxx/src/share/io/scream_scorpio_interface_iso_c2f.F90 b/components/eamxx/src/share/io/scream_scorpio_interface_iso_c2f.F90 deleted file mode 100644 index 2f1263beef7..00000000000 --- a/components/eamxx/src/share/io/scream_scorpio_interface_iso_c2f.F90 +++ /dev/null @@ -1,520 +0,0 @@ -module scream_scorpio_interface_iso_c2f - use iso_c_binding, only: c_int, c_double, c_float, c_bool, c_ptr - implicit none - -! -! This file contains bridges from scream c++ to shoc fortran. -! -contains -!=====================================================================! - subroutine eam_init_pio_subsystem_c2f(mpicom,compid) bind(c) - use scream_scorpio_interface, only : eam_init_pio_subsystem - integer(kind=c_int), value, intent(in) :: mpicom,compid - - call eam_init_pio_subsystem(mpicom,compid) - end subroutine eam_init_pio_subsystem_c2f -!=====================================================================! - subroutine eam_pio_finalize_c2f() bind(c) - use scream_scorpio_interface, only : eam_pio_finalize - - call eam_pio_finalize() - end subroutine eam_pio_finalize_c2f -!=====================================================================! - function get_file_ncid_c2f(filename_in) result(ncid) bind(c) - use scream_scorpio_interface, only : lookup_pio_atm_file, pio_atm_file_t - type(c_ptr), intent(in) :: filename_in - - type(pio_atm_file_t), pointer :: atm_file - character(len=256) :: filename - integer(kind=c_int) :: ncid - logical :: found - - call convert_c_string(filename_in,filename) - call lookup_pio_atm_file(filename,atm_file,found) - if (found) then - ncid = int(atm_file%pioFileDesc%fh,kind=c_int) - else - ncid = -1 - endif - end function get_file_ncid_c2f -!=====================================================================! - function get_file_mode_c2f(filename_in) result(mode) bind(c) - use scream_scorpio_interface, only : lookup_pio_atm_file, pio_atm_file_t - - type(c_ptr), intent(in) :: filename_in - - type(pio_atm_file_t), pointer :: atm_file - character(len=256) :: filename - integer(kind=c_int) :: mode - logical :: found - - call convert_c_string(filename_in,filename) - call lookup_pio_atm_file(filename,atm_file,found) - if (found) then - mode = atm_file%purpose - else - mode = 0 - endif - end function get_file_mode_c2f - function is_file_open_c2f(filename_in,purpose) result(res) bind(c) - use scream_scorpio_interface, only : lookup_pio_atm_file, pio_atm_file_t - - type(c_ptr), intent(in) :: filename_in - integer(kind=c_int), intent(in) :: purpose - - type(pio_atm_file_t), pointer :: atm_file - character(len=256) :: filename - logical (kind=c_bool) :: res - logical :: found - - call convert_c_string(filename_in,filename) - call lookup_pio_atm_file(filename,atm_file,found) - if (found) then - res = LOGICAL(purpose .lt. 0 .or. atm_file%purpose .eq. purpose,kind=c_bool) - else - res = .false. - endif - end function is_file_open_c2f -!=====================================================================! - subroutine register_file_c2f(filename_in,purpose,iotype) bind(c) - use scream_scorpio_interface, only : register_file - type(c_ptr), intent(in) :: filename_in - integer(kind=c_int), intent(in) :: purpose - integer(kind=c_int), intent(in) :: iotype - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call register_file(trim(filename),purpose,iotype) - - end subroutine register_file_c2f -!=====================================================================! - subroutine set_decomp_c2f(filename_in) bind(c) - use scream_scorpio_interface, only : set_decomp - type(c_ptr), intent(in) :: filename_in - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call set_decomp(trim(filename)) - end subroutine set_decomp_c2f -!=====================================================================! - subroutine set_dof_c2f(filename_in,varname_in,dof_len,dof_vec) bind(c) - use scream_scorpio_interface, only : set_dof, pio_offset_kind - use iso_c_binding, only: c_int64_t - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), value, intent(in) :: dof_len - integer(kind=c_int64_t), intent(in), dimension(dof_len) :: dof_vec - - character(len=256) :: filename - character(len=256) :: varname - integer :: ii - integer(kind=pio_offset_kind), allocatable :: dof_vec_f90(:) - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - ! Need to add 1 to the dof_vec because C++ starts indices at 0 not 1: - allocate(dof_vec_f90(dof_len)) - do ii = 1,dof_len - dof_vec_f90(ii) = dof_vec(ii) + 1 - end do - call set_dof(trim(filename),trim(varname),dof_len,dof_vec_f90) - deallocate(dof_vec_f90) - end subroutine set_dof_c2f -!=====================================================================! - subroutine eam_pio_closefile_c2f(filename_in) bind(c) - use scream_scorpio_interface, only : eam_pio_closefile - type(c_ptr), intent(in) :: filename_in - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call eam_pio_closefile(trim(filename)) - - end subroutine eam_pio_closefile_c2f -!=====================================================================! - subroutine eam_pio_flush_file_c2f(filename_in) bind(c) - use scream_scorpio_interface, only : eam_pio_flush_file - type(c_ptr), intent(in) :: filename_in - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call eam_pio_flush_file(trim(filename)) - - end subroutine eam_pio_flush_file_c2f -!=====================================================================! - subroutine pio_update_time_c2f(filename_in,time) bind(c) - use scream_scorpio_interface, only : eam_update_time - type(c_ptr), intent(in) :: filename_in - real(kind=c_double), value, intent(in) :: time - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call eam_update_time(trim(filename),time) - - end subroutine pio_update_time_c2f -!=====================================================================! - subroutine register_variable_c2f(filename_in, shortname_in, longname_in, & - units_in, numdims, var_dimensions_in, & - dtype, nc_dtype, pio_decomp_tag_in) bind(c) - use scream_scorpio_interface, only : register_variable - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: shortname_in - type(c_ptr), intent(in) :: longname_in - type(c_ptr), intent(in) :: units_in - integer(kind=c_int), value, intent(in) :: numdims - type(c_ptr), intent(in) :: var_dimensions_in(numdims) - integer(kind=c_int), value, intent(in) :: dtype, nc_dtype - type(c_ptr), intent(in) :: pio_decomp_tag_in - - character(len=256) :: filename - character(len=256) :: shortname - character(len=256) :: longname - character(len=256) :: units - character(len=256) :: var_dimensions(numdims) - character(len=256) :: pio_decomp_tag - integer :: ii - - call convert_c_string(filename_in,filename) - call convert_c_string(shortname_in,shortname) - call convert_c_string(longname_in,longname) - call convert_c_string(units_in,units) - call convert_c_string(pio_decomp_tag_in,pio_decomp_tag) - do ii = 1,numdims - call convert_c_string(var_dimensions_in(ii), var_dimensions(ii)) - end do - - call register_variable(filename,shortname,longname,units,numdims,var_dimensions,dtype,nc_dtype,pio_decomp_tag) - - end subroutine register_variable_c2f -!=====================================================================! - subroutine set_variable_metadata_char_c2f(filename_in, varname_in, metaname_in, metaval_in) bind(c) - use scream_scorpio_interface, only : set_variable_metadata_char - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - type(c_ptr), intent(in) :: metaval_in - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - character(len=256) :: metaval - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - call convert_c_string(metaval_in,metaval) - - call set_variable_metadata_char(filename,varname,metaname,metaval) - - end subroutine set_variable_metadata_char_c2f -!=====================================================================! - subroutine set_variable_metadata_float_c2f(filename_in, varname_in, metaname_in, metaval_in) bind(c) - use scream_scorpio_interface, only : set_variable_metadata_float - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - real(kind=c_float), value, intent(in) :: metaval_in - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - - call set_variable_metadata_float(filename,varname,metaname,metaval_in) - - end subroutine set_variable_metadata_float_c2f -!=====================================================================! - subroutine set_variable_metadata_double_c2f(filename_in, varname_in, metaname_in, metaval_in) bind(c) - use scream_scorpio_interface, only : set_variable_metadata_double - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - real(kind=c_double), value, intent(in) :: metaval_in - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - - call set_variable_metadata_double(filename,varname,metaname,metaval_in) - - end subroutine set_variable_metadata_double_c2f -!=====================================================================! - function get_variable_metadata_float_c2f(filename_in, varname_in, metaname_in) result(metaval_out) bind(c) - use scream_scorpio_interface, only : get_variable_metadata_float - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - real(kind=c_float) :: metaval_out - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - - metaval_out = get_variable_metadata_float(filename,varname,metaname) - - end function get_variable_metadata_float_c2f -!=====================================================================! - function get_variable_metadata_double_c2f(filename_in, varname_in, metaname_in) result(metaval_out) bind(c) - use scream_scorpio_interface, only : get_variable_metadata_double - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - real(kind=c_double) :: metaval_out - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - - metaval_out = get_variable_metadata_double(filename,varname,metaname) - - end function get_variable_metadata_double_c2f -!=====================================================================! - subroutine get_variable_metadata_char_c2f(filename_in, varname_in, metaname_in, metaval_out) bind(c) - use scream_scorpio_interface, only : get_variable_metadata_char - use iso_c_binding, only: C_NULL_CHAR, c_char, c_f_pointer - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - type(c_ptr), intent(in) :: metaname_in - type(c_ptr), intent(in) :: metaval_out - - character(len=256) :: filename - character(len=256) :: varname - character(len=256) :: metaname - character(len=256) :: metaval - character(len=256, kind=c_char), pointer :: temp_string - integer :: slen - - call c_f_pointer(metaval_out,temp_string) - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call convert_c_string(metaname_in,metaname) - - metaval = get_variable_metadata_char(filename,varname,metaname) - - slen = len(trim(metaval)) - ! If string is 255 or less, add terminating char. If not, it's still - ! ok (the C++ string will have length=max_length=256) - if (slen .le. 255) then - temp_string = trim(metaval) // C_NULL_CHAR - else - temp_string = metaval - endif - end subroutine get_variable_metadata_char_c2f -!=====================================================================! - subroutine register_dimension_c2f(filename_in, shortname_in, longname_in, length, partitioned) bind(c) - use scream_scorpio_interface, only : register_dimension - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: shortname_in - type(c_ptr), intent(in) :: longname_in - integer(kind=c_int), value, intent(in) :: length - logical(kind=c_bool), value, intent(in) :: partitioned - - character(len=256) :: filename - character(len=256) :: shortname - character(len=256) :: longname - - call convert_c_string(filename_in,filename) - call convert_c_string(shortname_in,shortname) - call convert_c_string(longname_in,longname) - call register_dimension(filename,shortname,longname,length,LOGICAL(partitioned)) - - end subroutine register_dimension_c2f -!=====================================================================! - function read_curr_time_c2f(filename_in) result(val) bind(c) - use scream_scorpio_interface, only : read_time_at_index - type(c_ptr), intent(in) :: filename_in - real(kind=c_double) :: val - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - val = read_time_at_index(filename) - - end function read_curr_time_c2f -!=====================================================================! - function read_time_at_index_c2f(filename_in,time_index) result(val) bind(c) - use scream_scorpio_interface, only : read_time_at_index - type(c_ptr), intent(in) :: filename_in - integer(kind=c_int), intent(in) :: time_index ! zero-based - real(kind=c_double) :: val - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - val = read_time_at_index(filename,time_index) - - end function read_time_at_index_c2f -!=====================================================================! - function is_enddef_c2f(filename_in) bind(c) result(enddef) - use scream_scorpio_interface, only : lookup_pio_atm_file, pio_atm_file_t - type(c_ptr), intent(in) :: filename_in - - type(pio_atm_file_t), pointer :: atm_file - character(len=256) :: filename - logical (kind=c_bool) :: enddef - logical :: found - - call convert_c_string(filename_in,filename) - call lookup_pio_atm_file(filename,atm_file,found) - if (found) then - enddef = LOGICAL(atm_file%is_enddef, kind=c_bool) - endif - end function is_enddef_c2f -!=====================================================================! - subroutine eam_pio_enddef_c2f(filename_in) bind(c) - use scream_scorpio_interface, only : eam_pio_enddef - type(c_ptr), intent(in) :: filename_in - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call eam_pio_enddef(filename) - end subroutine eam_pio_enddef_c2f -!=====================================================================! - subroutine eam_pio_redef_c2f(filename_in) bind(c) - use scream_scorpio_interface, only : eam_pio_redef - type(c_ptr), intent(in) :: filename_in - - character(len=256) :: filename - - call convert_c_string(filename_in,filename) - call eam_pio_redef(filename) - end subroutine eam_pio_redef_c2f -!=====================================================================! - subroutine convert_c_string(c_string_ptr,f_string) - use iso_c_binding, only: c_f_pointer, C_NULL_CHAR - ! Purpose: To convert a c_string pointer to the proper fortran string format. - type(c_ptr), intent(in) :: c_string_ptr - character(len=256), intent(out) :: f_string - character(len=256), pointer :: temp_string - integer :: str_len - - call c_f_pointer(c_string_ptr,temp_string) - str_len = index(temp_string, C_NULL_CHAR) - 1 - f_string = trim(temp_string(1:str_len)) - - end subroutine convert_c_string -!=====================================================================! - subroutine grid_write_data_array_c2f_int(filename_in,varname_in,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_write_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), intent(in), value :: buf_size - integer(kind=c_int), intent(in) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_write_data_array(filename,varname,buf,buf_size) - - end subroutine grid_write_data_array_c2f_int - subroutine grid_write_data_array_c2f_float(filename_in,varname_in,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_write_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), intent(in), value :: buf_size - real(kind=c_float), intent(in) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_write_data_array(filename,varname,buf,buf_size) - - end subroutine grid_write_data_array_c2f_float - subroutine grid_write_data_array_c2f_double(filename_in,varname_in,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_write_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), intent(in), value :: buf_size - real(kind=c_double), intent(in) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_write_data_array(filename,varname,buf,buf_size) - - end subroutine grid_write_data_array_c2f_double -!=====================================================================! - subroutine grid_read_data_array_c2f_int(filename_in,varname_in,time_index,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_read_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), value, intent(in) :: time_index ! zero-based - integer(kind=c_int), intent(in), value :: buf_size - integer(kind=c_int), intent(out) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_read_data_array(filename,varname,buf,buf_size,time_index+1) - - end subroutine grid_read_data_array_c2f_int -!=====================================================================! - subroutine grid_read_data_array_c2f_float(filename_in,varname_in,time_index,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_read_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), value, intent(in) :: time_index ! zero-based - integer(kind=c_int), intent(in), value :: buf_size - real(kind=c_float), intent(out) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_read_data_array(filename,varname,buf,buf_size,time_index+1) - - end subroutine grid_read_data_array_c2f_float -!=====================================================================! - subroutine grid_read_data_array_c2f_double(filename_in,varname_in,time_index,buf,buf_size) bind(c) - use scream_scorpio_interface, only: grid_read_data_array - - type(c_ptr), intent(in) :: filename_in - type(c_ptr), intent(in) :: varname_in - integer(kind=c_int), value, intent(in) :: time_index ! zero-based - integer(kind=c_int), intent(in), value :: buf_size - real(kind=c_double), intent(out) :: buf(buf_size) - - character(len=256) :: filename - character(len=256) :: varname - - call convert_c_string(filename_in,filename) - call convert_c_string(varname_in,varname) - call grid_read_data_array(filename,varname,buf,buf_size,time_index+1) - - end subroutine grid_read_data_array_c2f_double -!=====================================================================! -end module scream_scorpio_interface_iso_c2f diff --git a/components/eamxx/src/share/io/scream_scorpio_types.cpp b/components/eamxx/src/share/io/scream_scorpio_types.cpp new file mode 100644 index 00000000000..71c3325c465 --- /dev/null +++ b/components/eamxx/src/share/io/scream_scorpio_types.cpp @@ -0,0 +1,59 @@ +#include "scream_scorpio_types.hpp" + +#include + +namespace scream { +namespace scorpio { + +std::string e2str (const FileMode mode) +{ + auto mode_int = static_cast::type>(mode); + std::string s; + switch (mode) { + case Unset: s = "UNSET"; break; + case Read: s = "READ"; break; + case Write: s = "WRITE"; break; + case Append: s = "APPEND"; break; + default: + EKAT_ERROR_MSG ( + "Error! Unsupported/unrecognized FileMode value.\n" + " - value: " + std::to_string(mode_int) + "\n"); + } + return s; +} + +IOType str2iotype(const std::string &str) +{ + if(str == "default"){ + return IOType::DefaultIOType; + } else if(str == "netcdf") { + return IOType::NetCDF; + } else if(str == "pnetcdf") { + return IOType::PnetCDF; + } else if(str == "adios") { + return IOType::Adios; + } else if(str == "hdf5") { + return IOType::Hdf5; + } else { + return IOType::Invalid; + } +} + +std::string iotype2str(const IOType iotype) +{ + std::string s; + switch(iotype){ + case IOType::DefaultIOType: s = "default"; break; + case IOType::NetCDF: s = "netcdf"; break; + case IOType::PnetCDF: s = "pnetcdf"; break; + case IOType::Adios: s = "adios"; break; + case IOType::Hdf5: s = "hdf5"; break; + case IOType::Invalid: s = "invalid"; break; + default: + EKAT_ERROR_MSG ("Unrecognized iotype.\n"); + } + return s; +} + +} // namespace scorpio +} // namespace scream diff --git a/components/eamxx/src/share/io/scream_scorpio_types.hpp b/components/eamxx/src/share/io/scream_scorpio_types.hpp new file mode 100644 index 00000000000..645b1b26937 --- /dev/null +++ b/components/eamxx/src/share/io/scream_scorpio_types.hpp @@ -0,0 +1,135 @@ +#ifndef SCREAM_SCORPIO_TYPES_HPP +#define SCREAM_SCORPIO_TYPES_HPP + +#include +#include +#include +#include + +namespace scream { +namespace scorpio { + +// Enum denoting how we open a file +enum FileMode { + Unset = 0, + Read = 1, + Write = 2, + Append = Read | Write +}; +std::string e2str (const FileMode mode); + +// I/O types supported +enum IOType { + // Default I/O type is used to let the code choose I/O type as needed (via CIME) + DefaultIOType = 0, + NetCDF, + PnetCDF, + Adios, + Hdf5, + Invalid +}; + +IOType str2iotype(const std::string &str); +std::string iotype2str(const IOType iotype); + +// The type used by PIOc for offsets +using offset_t = std::int64_t; + +/* + * The following PIOxyz types each represent an entity that + * is associated in scorpio with an id. For each of them, we add some + * additional metadata, to avoid having to call PIOc_inq_xyz every time + * we need such information. + * While these types are defined in this public header, the customers + * of scream_io will never be able to get any object of these types out + * of the internal database. In particular, all data is stored in a + * ScorpioSession singleton class, whose declaration is hidden inside + * scream_scorpio_interface.cpp. + */ + +// The basic common data of any PIO entity +struct PIOEntity { + int ncid = -1; // PIO access all data via their id + std::string name; // In EAMxx, we prefer to use a string name than a number id +}; + +// An entity that is linked to a specific file +// This allows an entity to retrieve its file +struct PIOFileEntity : public PIOEntity { + int fid = -1; // Allow retrieving file from the entity +}; + +// A dimension +struct PIODim : public PIOFileEntity { + int length = -1; + bool unlimited = false; + + // In case we decompose the dimension, this will store + // the owned offsets on this rank + // NOTE: use a pointer, so we can detect if a decomposition already + // existed or not when we set one. + std::shared_ptr> offsets; +}; + +// A decomposition +// NOTE: the offsets of a PIODecomp are not the same as the offsets of +// stored in its dim. The latter are the offsets *along that dim*. +// A PIODecomp is associated with a Nd layout, which includes decomp_dim +// among its dimensions. PIODecomp::offsets are the offsets of the full +// array layout owned by this rank. Hence, there can be many PIODecomp +// all storing the same dim +struct PIODecomp : public PIOEntity { + std::vector offsets; // Owned offsets + std::shared_ptr dim; +}; + +// A variable +struct PIOVar : public PIOFileEntity { + // Note: if time_dep=true, we will add it to the list of dims passed + // to scorpio, but the time dim will not appear in this list. + std::vector> dims; + + std::vector dim_names () const { + std::vector n; + for (auto d : dims) { + n.push_back(d->name); + } + return n; + } + + std::string dtype; + std::string nc_dtype; + std::string units; + + bool time_dep = false; + + // Extra safety measure: for time_dep vars, use this to check that + // we are not writing more slices than the current time dim length. + int num_records = 0; + + std::shared_ptr decomp; + + // Used only if a) var is not decomposed, and b) dtype!=nc_dtype + int size = -1; // Product of all dims + std::vector buf; +}; + +// A file, which is basically a container for dims and vars +struct PIOFile : public PIOEntity { + std::map> dims; + std::map> vars; + + std::shared_ptr time_dim; + FileMode mode; + IOType iotype; + bool enddef = false; + + // We keep track of how many places are currently using this file, so that we + // can close it only when they are all done. + int num_customers = 0; +}; + +} // namespace scorpio +} // namespace scream + +#endif // define SCREAM_SCORPIO_INTERFACE_HPP diff --git a/components/eamxx/src/share/io/scream_shr_interface_c2f.F90 b/components/eamxx/src/share/io/scream_shr_interface_c2f.F90 new file mode 100644 index 00000000000..514a1478648 --- /dev/null +++ b/components/eamxx/src/share/io/scream_shr_interface_c2f.F90 @@ -0,0 +1,53 @@ +module eamxx_shr_interface_mod + use iso_c_binding, only: c_int + implicit none + +! +! This file contains bridges from EAMxx to E3SM shr module +! +contains + +!=====================================================================! + function shr_get_iosysid_c2f(atm_id) result (iosysid) bind(c) + use pio, only: iosystem_desc_t + use shr_pio_mod, only: shr_pio_getiosys + integer(kind=c_int), value, intent(in) :: atm_id + + integer (kind=c_int) :: iosysid + type(iosystem_desc_t), pointer :: iosystem + + iosystem => shr_pio_getiosys(atm_id) + iosysid = iosystem%iosysid + end function shr_get_iosysid_c2f + +!=====================================================================! + function shr_get_iotype_c2f(atm_id) result (iotype) bind(c) + use shr_pio_mod, only: shr_pio_getiotype + integer(kind=c_int), value, intent(in) :: atm_id + + integer (kind=c_int) :: iotype + + iotype = shr_pio_getiotype(atm_id) + end function shr_get_iotype_c2f + +!=====================================================================! + function shr_get_rearranger_c2f(atm_id) result (rearr) bind(c) + use shr_pio_mod, only: shr_pio_getrearranger + integer(kind=c_int), value, intent(in) :: atm_id + + integer (kind=c_int) :: rearr + + rearr = shr_pio_getrearranger(atm_id) + end function shr_get_rearranger_c2f + +!=====================================================================! + function shr_get_ioformat_c2f(atm_id) result (ioformat) bind(c) + use shr_pio_mod, only: shr_pio_getioformat + integer(kind=c_int), value, intent(in) :: atm_id + + integer (kind=c_int) :: ioformat + + ioformat = shr_pio_getioformat(atm_id) + end function shr_get_ioformat_c2f + +end module eamxx_shr_interface_mod diff --git a/components/eamxx/src/share/io/scream_shr_interface_c2f.hpp b/components/eamxx/src/share/io/scream_shr_interface_c2f.hpp new file mode 100644 index 00000000000..4e861542515 --- /dev/null +++ b/components/eamxx/src/share/io/scream_shr_interface_c2f.hpp @@ -0,0 +1,16 @@ +#ifndef SCREAM_SHR_INTERFACE_HPP +#define SCREAM_SHR_INTERFACE_HPP + +extern "C" +{ + +#ifdef SCREAM_CIME_BUILD +int shr_get_iosysid_c2f(int atm_id); +int shr_get_iotype_c2f(int atm_id); +int shr_get_rearranger_c2f(int atm_id); +int shr_get_ioformat_c2f(int atm_id); +#endif + +} // extern "C" + +#endif // SCREAM_SHR_INTERFACE_HPP diff --git a/components/eamxx/src/share/io/tests/CMakeLists.txt b/components/eamxx/src/share/io/tests/CMakeLists.txt index 94bcee1790b..16ce2efd437 100644 --- a/components/eamxx/src/share/io/tests/CMakeLists.txt +++ b/components/eamxx/src/share/io/tests/CMakeLists.txt @@ -5,6 +5,12 @@ include(ScreamUtils) include (BuildCprnc) BuildCprnc() +## Test scorpio interfaces +CreateUnitTest(scorpio_interface_tests "scorpio_interface_tests.cpp" + LIBS scream_scorpio_interface LABELS "io" + MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS} +) + ## Test io utils CreateUnitTest(io_utils "io_utils.cpp" LIBS scream_io LABELS io diff --git a/components/eamxx/src/share/io/tests/io_basic.cpp b/components/eamxx/src/share/io/tests/io_basic.cpp index 378a956f893..11905fd8480 100644 --- a/components/eamxx/src/share/io/tests/io_basic.cpp +++ b/components/eamxx/src/share/io/tests/io_basic.cpp @@ -257,14 +257,12 @@ void read (const std::string& avg_type, const std::string& freq_units, } // Check that the expected metadata was appropriately set for each variable - Real fill_out; - std::string att_test; for (const auto& fn: fnames) { - scorpio::get_variable_metadata(filename,fn,"_FillValue",fill_out); - REQUIRE(fill_out==constants::DefaultFillValue().value); + auto att_fill = scorpio::get_attribute(filename,fn,"_FillValue"); + REQUIRE(att_fill==constants::DefaultFillValue().value); - scorpio::get_variable_metadata(filename,fn,"test",att_test); - REQUIRE (att_test==fn); + auto att_str = scorpio::get_attribute(filename,fn,"test"); + REQUIRE (att_str==fn); } } @@ -284,7 +282,7 @@ TEST_CASE ("io_basic") { }; ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); auto seed = get_random_test_seed(&comm); @@ -308,7 +306,7 @@ TEST_CASE ("io_basic") { print(" PASS\n"); } } - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // anonymous namespace diff --git a/components/eamxx/src/share/io/tests/io_diags.cpp b/components/eamxx/src/share/io/tests/io_diags.cpp index b2fe7f994db..7daa13c1904 100644 --- a/components/eamxx/src/share/io/tests/io_diags.cpp +++ b/components/eamxx/src/share/io/tests/io_diags.cpp @@ -250,7 +250,7 @@ void read (const int seed, const ekat::Comm& comm) TEST_CASE ("io_diags") { ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); // Make MyDiag available via diag factory auto& diag_factory = AtmosphereDiagnosticFactory::instance(); @@ -272,7 +272,7 @@ TEST_CASE ("io_diags") { write(seed,comm); read(seed,comm); print(" PASS\n"); - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // anonymous namespace diff --git a/components/eamxx/src/share/io/tests/io_filled.cpp b/components/eamxx/src/share/io/tests/io_filled.cpp index ba5eb7d871d..0901e7e7c9e 100644 --- a/components/eamxx/src/share/io/tests/io_filled.cpp +++ b/components/eamxx/src/share/io/tests/io_filled.cpp @@ -247,10 +247,10 @@ void read (const std::string& avg_type, const std::string& freq_units, } // Check that the fill value gets appropriately set for each variable - Real fill_out; for (const auto& fn: fnames) { - scorpio::get_variable_metadata(filename,fn,"_FillValue",fill_out); - REQUIRE(fill_out==constants::DefaultFillValue().value); + // NOTE: use float, since default fp_precision for I/O is 'single' + auto att_fill = scorpio::get_attribute(filename,fn,"_FillValue"); + REQUIRE(att_fill==constants::DefaultFillValue().value); } } @@ -270,7 +270,7 @@ TEST_CASE ("io_filled") { }; ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); auto seed = get_random_test_seed(&comm); @@ -294,7 +294,7 @@ TEST_CASE ("io_filled") { print(" PASS\n"); } } - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // anonymous namespace diff --git a/components/eamxx/src/share/io/tests/io_monthly.cpp b/components/eamxx/src/share/io/tests/io_monthly.cpp index 4cf18fff1c7..82b06421196 100644 --- a/components/eamxx/src/share/io/tests/io_monthly.cpp +++ b/components/eamxx/src/share/io/tests/io_monthly.cpp @@ -212,7 +212,7 @@ void read (const int seed, const ekat::Comm& comm) TEST_CASE ("io_monthly") { ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); auto seed = get_random_test_seed(&comm); @@ -224,7 +224,7 @@ TEST_CASE ("io_monthly") { if (comm.am_i_root()) { std::cout << " -> Testing output with one file per month ... PASS\n"; } - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // anonymous namespace diff --git a/components/eamxx/src/share/io/tests/io_packed.cpp b/components/eamxx/src/share/io/tests/io_packed.cpp index 585fc7b26f6..5796dbbe8b2 100644 --- a/components/eamxx/src/share/io/tests/io_packed.cpp +++ b/components/eamxx/src/share/io/tests/io_packed.cpp @@ -178,7 +178,7 @@ void read (const int freq, const int seed, const int ps_write, const int ps_read TEST_CASE ("io_packs") { ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); auto seed = get_random_test_seed(&comm); @@ -202,7 +202,7 @@ TEST_CASE ("io_packs") { print(" PASS\n"); } } - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // anonymous namespace diff --git a/components/eamxx/src/share/io/tests/io_remap_test.cpp b/components/eamxx/src/share/io/tests/io_remap_test.cpp index bfed937636e..c579dea5c7d 100644 --- a/components/eamxx/src/share/io/tests/io_remap_test.cpp +++ b/components/eamxx/src/share/io/tests/io_remap_test.cpp @@ -54,8 +54,7 @@ TEST_CASE("io_remap_test","io_remap_test") print (" -> Test Setup ...\n",io_comm); - MPI_Fint fcomm = MPI_Comm_c2f(io_comm.mpi_comm()); // MPI communicator group used for I/O. In our simple test we use MPI_COMM_WORLD, however a subset could be used. - scorpio::eam_init_pio_subsystem(fcomm); // Gather the initial PIO subsystem data creater by component coupler + scorpio::init_subsystem(io_comm); const int ncols_src = 64*io_comm.size(); const int nlevs_src = 2*packsize + 1; // Construct a timestamp @@ -81,7 +80,8 @@ TEST_CASE("io_remap_test","io_remap_test") print (" -> Create remap file ... \n",io_comm); const int ncols_tgt_l = ncols_src_l/2; const int ncols_tgt = ncols_src/2; - std::vector col, row, S; + std::vector col, row; + std::vector S; const Real wgt = 0.4; for (int ii=0; ii dofs_cols (ncols_src_l); - std::iota(dofs_cols.begin(),dofs_cols.end(),io_comm.rank()*ncols_src_l); // For vertical remapping we will prokject onto a set of equally // spaced pressure levels from p_top to b_bot that is nearly half // the number of source columns. - std::vector dofs_levs(nlevs_tgt); - std::iota(dofs_levs.begin(),dofs_levs.end(),0); std::vector p_tgt; for (int ii=0; ii Create remap file ... done\n",io_comm); /* @@ -296,11 +288,11 @@ TEST_CASE("io_remap_test","io_remap_test") std::string att_val; const auto& filename = vert_in.get("Filename"); for (auto& fname : fnames) { - scorpio::get_variable_metadata(filename,fname,"test",att_val); + att_val = scorpio::get_attribute(filename,fname,"test"); REQUIRE (att_val==fname); } std::string f_at_lev_name = "Y_int_at_" + std::to_string(p_ref) + "Pa"; - scorpio::get_variable_metadata(filename,f_at_lev_name,"test",att_val); + att_val = scorpio::get_attribute(filename,f_at_lev_name,"test"); REQUIRE (att_val=="Y_int"); test_input.finalize(); @@ -366,11 +358,11 @@ TEST_CASE("io_remap_test","io_remap_test") std::string att_val; const auto& filename = horiz_in.get("Filename"); for (auto& fname : fnames) { - scorpio::get_variable_metadata(filename,fname,"test",att_val); + att_val = scorpio::get_attribute(filename,fname,"test"); REQUIRE (att_val==fname); } std::string f_at_lev_name = "Y_int_at_" + std::to_string(p_ref) + "Pa"; - scorpio::get_variable_metadata(filename,f_at_lev_name,"test",att_val); + att_val = scorpio::get_attribute(filename,f_at_lev_name,"test"); REQUIRE (att_val=="Y_int"); test_input.finalize(); @@ -452,11 +444,11 @@ TEST_CASE("io_remap_test","io_remap_test") std::string att_val; const auto& filename = vh_in.get("Filename"); for (auto& fname : fnames) { - scorpio::get_variable_metadata(filename,fname,"test",att_val); + att_val = scorpio::get_attribute(filename,fname,"test"); REQUIRE (att_val==fname); } std::string f_at_lev_name = "Y_int_at_" + std::to_string(p_ref) + "Pa"; - scorpio::get_variable_metadata(filename,f_at_lev_name,"test",att_val); + att_val = scorpio::get_attribute(filename,f_at_lev_name,"test"); REQUIRE (att_val=="Y_int"); test_input.finalize(); @@ -551,7 +543,7 @@ TEST_CASE("io_remap_test","io_remap_test") // ------------------------------------------------------------------------------------------------------ // All Done print (" -> Test Remapped Output ... done\n",io_comm); - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } /*==========================================================================================================*/ diff --git a/components/eamxx/src/share/io/tests/io_se_grid.cpp b/components/eamxx/src/share/io/tests/io_se_grid.cpp index edf5f5dbbcd..b610002c8ac 100644 --- a/components/eamxx/src/share/io/tests/io_se_grid.cpp +++ b/components/eamxx/src/share/io/tests/io_se_grid.cpp @@ -47,8 +47,7 @@ TEST_CASE("se_grid_io") int num_levs = 2 + SCREAM_PACK_SIZE; // Initialize the pio_subsystem for this test: - MPI_Fint fcomm = MPI_Comm_c2f(io_comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); // Gather the initial PIO subsystem data creater by component coupler + scorpio::init_subsystem(io_comm); // First set up a field manager and grids manager to interact with the output functions auto gm = get_test_gm(io_comm,num_my_elems,np,num_levs); @@ -95,7 +94,7 @@ TEST_CASE("se_grid_io") ins_input.finalize(); // All Done - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } /*===================================================================================================*/ diff --git a/components/eamxx/src/share/io/tests/output_restart.cpp b/components/eamxx/src/share/io/tests/output_restart.cpp index 7e59574b1b9..40c8ffee528 100644 --- a/components/eamxx/src/share/io/tests/output_restart.cpp +++ b/components/eamxx/src/share/io/tests/output_restart.cpp @@ -68,8 +68,7 @@ TEST_CASE("output_restart","io") const auto& out_fields = fm0->get_groups_info().at("output")->m_fields_names; // Initialize the pio_subsystem for this test: - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); // Timestamp of the simulation initial time util::TimeStamp t0 ({2000,1,1},{0,0,0}); @@ -150,7 +149,7 @@ TEST_CASE("output_restart","io") print(" DONE\n"); } // Finalize everything - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } /*=============================================================================================*/ diff --git a/components/eamxx/src/share/io/tests/scorpio_interface_tests.cpp b/components/eamxx/src/share/io/tests/scorpio_interface_tests.cpp new file mode 100644 index 00000000000..ea2999eef57 --- /dev/null +++ b/components/eamxx/src/share/io/tests/scorpio_interface_tests.cpp @@ -0,0 +1,217 @@ +#include + +#include "share/io/scream_scorpio_interface.hpp" +#include + +namespace scream { + +using namespace scorpio; + +TEST_CASE ("io_subsystem") { + ekat::Comm comm (MPI_COMM_WORLD); + + init_subsystem (comm); + REQUIRE_THROWS (init_subsystem(comm)); // ERROR: already inited + + finalize_subsystem (); + REQUIRE_THROWS (finalize_subsystem()); // ERROR: no subsystem active +} + +TEST_CASE ("write_and_read") { + ekat::Comm comm (MPI_COMM_WORLD); + + EKAT_REQUIRE_MSG (comm.size()<=4, + "Error! This test is tailored for an MPI_Comm of size 1, 2, 3, or 4.\n" + " - MPI_Comm size: " + std::to_string(comm.size()) + "\n"); + + init_subsystem (comm); + + std::string filename = "scorpio_interface_write_test_np" + std::to_string(comm.size()) + ".nc"; + + const int dim1 = 2; + const int dim2 = 4; + const int dim3 = 12; + const int ldim3 = dim3 / comm.size(); + + // Offsets for dim3 decomp owned by this rank + std::vector my_offsets; + for (int i=0; i var1 (dim1); + std::vector var2 (dim1*dim2); + std::vector var3 (1); + std::vector var45 (ldim3*dim1); + + // Write first time slice + update_time (filename,0.0); + std::iota (var1.begin(),var1.end(),100); + std::iota (var2.begin(),var2.end(),100); + std::iota (var3.begin(),var3.end(),100); + std::iota (var45.begin(),var45.end(),100+comm.rank()*ldim3*dim1); + write_var (filename,"var1",var1.data()); + write_var (filename,"var2",var2.data()); + write_var (filename,"var3",var3.data()); + write_var (filename,"var4",var45.data()); + write_var (filename,"var5",var45.data()); + + REQUIRE_THROWS (write_var (filename,"var3",static_cast(nullptr))); // ERROR: invalid pointer + + // Write second time slice + update_time (filename,0.5); + std::iota (var2.begin(),var2.end(), 200); + std::iota (var3.begin(),var3.end(), 200); + std::iota (var45.begin(),var45.end(),200+comm.rank()*ldim3*dim1); + write_var (filename,"var2",var2.data()); + write_var (filename,"var3",var3.data()); + double minus_one = -1; + write_var (filename,"var5",var45.data(),&minus_one); + + // Cleanup + release_file (filename); + + REQUIRE_THROWS (release_file (filename)); // ERROR: file not open + REQUIRE (not is_file_open(filename)); + } + + // Read phase + { + using strvec_t = std::vector; + + register_file (filename,Read); + REQUIRE (is_file_open(filename)); + + // Check dims + REQUIRE (has_dim(filename,"the_time",2)); + REQUIRE (has_dim(filename,"dim1",dim1)); + REQUIRE (has_dim(filename,"dim2",dim2)); + REQUIRE (has_dim(filename,"dim3",dim3)); + + // Check vars + REQUIRE (has_var(filename,"the_time")); + const auto& timevar = get_var(filename,"the_time"); + REQUIRE (timevar.dim_names().size()==0); + REQUIRE (timevar.time_dep==true); + + REQUIRE (has_var(filename,"var1")); + const auto& piovar1 = get_var(filename,"var1"); + REQUIRE (piovar1.dim_names()==strvec_t{"dim1"}); + REQUIRE (piovar1.time_dep==false); + + REQUIRE (has_var(filename,"var2")); + const auto& piovar2 = get_var(filename,"var2"); + REQUIRE (piovar2.dim_names()==strvec_t{"dim1","dim2"}); + REQUIRE (piovar2.time_dep==true); + + REQUIRE (has_var(filename,"var3")); + const auto& piovar3 = get_var(filename,"var3"); + REQUIRE (piovar3.dim_names().size()==0); + REQUIRE (piovar3.time_dep==true); + + REQUIRE (has_var(filename,"var4")); + const auto& piovar4 = get_var(filename,"var4"); + REQUIRE (piovar4.dim_names()==strvec_t{"dim3","dim1"}); + REQUIRE (piovar4.time_dep==false); + + REQUIRE (has_var(filename,"var5")); + const auto piovar5= get_var(filename,"var5"); + REQUIRE (piovar5.dim_names()==strvec_t{"dim3","dim1"}); + REQUIRE (piovar5.time_dep==true); + + REQUIRE (not has_var(filename,"var0")); // Var not in file + + set_dim_decomp (filename,"dim3",my_offsets); + + std::vector var1 (dim1); + std::vector var2 (dim1*dim2); + std::vector var3 (1); + std::vector var45 (ldim3*dim1); + + std::vector tgt_var1 (dim1); + std::vector tgt_var2 (dim1*dim2); + std::vector tgt_var3 (1); + std::vector tgt_var45 (ldim3*dim1); + std::iota (tgt_var1.begin(),tgt_var1.end(), 100); + std::iota (tgt_var2.begin(),tgt_var2.end(), 100); + std::iota (tgt_var3.begin(),tgt_var3.end(), 100); + std::iota (tgt_var45.begin(),tgt_var45.end(),100+comm.rank()*ldim3*dim1); + + read_var (filename,"var1",var1.data()); + REQUIRE (tgt_var1==var1); + + // Read first time slice + REQUIRE_THROWS (read_var (filename,"var3",var3.data(),3)); // ERROR: time_idx out of bounds + REQUIRE_THROWS (read_var (filename,"var3",static_cast(nullptr))); // ERROR: invalid pointer + + read_var (filename,"var2",var2.data(),0); + REQUIRE (tgt_var2==var2); + + read_var (filename,"var3",var3.data(),0); + REQUIRE (tgt_var3==var3); + + read_var (filename,"var4",var45.data(),0); + REQUIRE (tgt_var45==var45); + + read_var (filename,"var5",var45.data(),0); + REQUIRE (tgt_var45==var45); + + // Read second time slice + std::iota (tgt_var2.begin(),tgt_var2.end(), 200); + std::iota (tgt_var3.begin(),tgt_var3.end(), 200); + std::iota (tgt_var45.begin(),tgt_var45.end(),200+comm.rank()*ldim3*dim1); + + read_var (filename,"var2",var2.data(),1); + REQUIRE (tgt_var2==var2); + + read_var (filename,"var3",var3.data(),1); + REQUIRE (tgt_var3==var3); + + read_var (filename,"var5",var45.data(),1); + REQUIRE (tgt_var45==var45); + + // Cleanup + release_file (filename); + } + + finalize_subsystem (); +} + +} // namespace scream diff --git a/components/eamxx/src/share/tests/coarsening_remapper_tests.cpp b/components/eamxx/src/share/tests/coarsening_remapper_tests.cpp index 7f1a3c20c7b..c6223b67dbf 100644 --- a/components/eamxx/src/share/tests/coarsening_remapper_tests.cpp +++ b/components/eamxx/src/share/tests/coarsening_remapper_tests.cpp @@ -236,22 +236,15 @@ void create_remap_file(const std::string& filename, const int ngdofs_tgt) scorpio::register_file(filename, scorpio::FileMode::Write); - scorpio::register_dimension(filename,"n_a", "n_a", ngdofs_src, true); - scorpio::register_dimension(filename,"n_b", "n_b", ngdofs_tgt, true); - scorpio::register_dimension(filename,"n_s", "n_s", nnz, true); + scorpio::define_dim(filename,"n_a", ngdofs_src); + scorpio::define_dim(filename,"n_b", ngdofs_tgt); + scorpio::define_dim(filename,"n_s", nnz); - scorpio::register_variable(filename,"col","col","none",{"n_s"},"int","int","int-nnz"); - scorpio::register_variable(filename,"row","row","none",{"n_s"},"int","int","int-nnz"); - scorpio::register_variable(filename,"S","S","none",{"n_s"},"double","double","Real-nnz"); + scorpio::define_var(filename,"col",{"n_s"},"int"); + scorpio::define_var(filename,"row",{"n_s"},"int"); + scorpio::define_var(filename,"S" ,{"n_s"},"double"); - std::vector dofs(nnz); - std::iota(dofs.begin(),dofs.end(),0); - - scorpio::set_dof(filename,"col",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"row",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"S", dofs.size(),dofs.data()); - - scorpio::eam_pio_enddef(filename); + scorpio::enddef(filename); std::vector col(nnz), row(nnz); std::vector S(nnz,0.5); @@ -262,11 +255,11 @@ void create_remap_file(const std::string& filename, const int ngdofs_tgt) col[2*i+1] = i+1; } - scorpio::grid_write_data_array(filename,"row",row.data(),nnz); - scorpio::grid_write_data_array(filename,"col",col.data(),nnz); - scorpio::grid_write_data_array(filename,"S", S.data(),nnz); + scorpio::write_var(filename,"row",row.data()); + scorpio::write_var(filename,"col",col.data()); + scorpio::write_var(filename,"S", S.data()); - scorpio::eam_pio_closefile(filename); + scorpio::release_file(filename); } TEST_CASE("coarsening_remap") @@ -288,8 +281,7 @@ TEST_CASE("coarsening_remap") root_print (" | Testing coarsening remapper |\n",comm); root_print (" +---------------------------------+\n\n",comm); - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); auto engine = setup_random_test (&comm); // -------------------------------------- // @@ -507,7 +499,7 @@ TEST_CASE("coarsening_remap") } // Clean up scorpio stuff - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // namespace scream diff --git a/components/eamxx/src/share/tests/eamxx_time_interpolation_tests.cpp b/components/eamxx/src/share/tests/eamxx_time_interpolation_tests.cpp index 53e423dfa4d..c8551d08126 100644 --- a/components/eamxx/src/share/tests/eamxx_time_interpolation_tests.cpp +++ b/components/eamxx/src/share/tests/eamxx_time_interpolation_tests.cpp @@ -139,7 +139,7 @@ TEST_CASE ("eamxx_time_interpolation_data_from_file") { // Setup basic test printf(" - Test Basics...\n"); ekat::Comm comm(MPI_COMM_WORLD); - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); auto seed = get_random_test_seed(&comm); std::mt19937_64 engine(seed); const auto t0 = init_timestamp(); @@ -229,7 +229,7 @@ TEST_CASE ("eamxx_time_interpolation_data_from_file") { printf(" ... DONE\n"); // All done with IO - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); printf("TimeInterpolation - From File Case...DONE\n\n\n"); } // TEST_CASE eamxx_time_interpolation_data_from_file @@ -350,7 +350,9 @@ std::shared_ptr get_fm (const std::shared_ptr& const auto units = ekat::units::Units::nondimensional(); for (const auto& fl : layouts) { - FID fid("f_"+std::to_string(fl.size()),fl,units,grid->name()); + int gl_size = fl.size(); + grid->get_comm().all_reduce(&gl_size,1,MPI_SUM); + FID fid("f_"+std::to_string(gl_size),fl,units,grid->name()); Field f(fid); f.allocate_view(); randomize (f,engine,my_pdf); diff --git a/components/eamxx/src/share/tests/refining_remapper_p2p_tests.cpp b/components/eamxx/src/share/tests/refining_remapper_p2p_tests.cpp index 75ea20f8130..56d0de8a47a 100644 --- a/components/eamxx/src/share/tests/refining_remapper_p2p_tests.cpp +++ b/components/eamxx/src/share/tests/refining_remapper_p2p_tests.cpp @@ -123,21 +123,15 @@ void write_map_file (const std::string& filename, const int ngdofs_src) { scorpio::register_file(filename, scorpio::FileMode::Write); - scorpio::register_dimension(filename, "n_a", "n_a", ngdofs_src, false); - scorpio::register_dimension(filename, "n_b", "n_b", ngdofs_tgt, false); - scorpio::register_dimension(filename, "n_s", "n_s", nnz, false); - - scorpio::register_variable(filename, "col", "col", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "row", "row", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "S", "S", "1", {"n_s"}, "double", "double", ""); - - std::vector dofs(nnz); - std::iota(dofs.begin(),dofs.end(),0); - scorpio::set_dof(filename,"col",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"row",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"S", dofs.size(),dofs.data()); - - scorpio::eam_pio_enddef(filename); + scorpio::define_dim(filename, "n_a", ngdofs_src); + scorpio::define_dim(filename, "n_b", ngdofs_tgt); + scorpio::define_dim(filename, "n_s", nnz); + + scorpio::define_var(filename, "col", {"n_s"}, "int"); + scorpio::define_var(filename, "row", {"n_s"}, "int"); + scorpio::define_var(filename, "S", {"n_s"}, "double"); + + scorpio::enddef(filename); std::vector col(nnz), row(nnz); std::vector S(nnz); @@ -156,11 +150,11 @@ void write_map_file (const std::string& filename, const int ngdofs_src) { S[ngdofs_src+2*i+1] = 0.5; } - scorpio::grid_write_data_array(filename,"row",row.data(),nnz); - scorpio::grid_write_data_array(filename,"col",col.data(),nnz); - scorpio::grid_write_data_array(filename,"S", S.data(), nnz); + scorpio::write_var(filename,"row",row.data()); + scorpio::write_var(filename,"col",col.data()); + scorpio::write_var(filename,"S", S.data()); - scorpio::eam_pio_closefile(filename); + scorpio::release_file(filename); } TEST_CASE ("refining_remapper") { @@ -172,8 +166,7 @@ TEST_CASE ("refining_remapper") { auto engine = setup_random_test (&comm); - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); // Create a map file const int ngdofs_src = 4*comm.size(); @@ -417,7 +410,7 @@ TEST_CASE ("refining_remapper") { // Clean up r = nullptr; - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // namespace scream diff --git a/components/eamxx/src/share/tests/refining_remapper_rma_tests.cpp b/components/eamxx/src/share/tests/refining_remapper_rma_tests.cpp index 53fb79321d4..b37bd2df139 100644 --- a/components/eamxx/src/share/tests/refining_remapper_rma_tests.cpp +++ b/components/eamxx/src/share/tests/refining_remapper_rma_tests.cpp @@ -178,21 +178,15 @@ void write_map_file (const std::string& filename, const int ngdofs_src) { scorpio::register_file(filename, scorpio::FileMode::Write); - scorpio::register_dimension(filename, "n_a", "n_a", ngdofs_src, false); - scorpio::register_dimension(filename, "n_b", "n_b", ngdofs_tgt, false); - scorpio::register_dimension(filename, "n_s", "n_s", nnz, false); - - scorpio::register_variable(filename, "col", "col", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "row", "row", "1", {"n_s"}, "int", "int", ""); - scorpio::register_variable(filename, "S", "S", "1", {"n_s"}, "double", "double", ""); - - std::vector dofs(nnz); - std::iota(dofs.begin(),dofs.end(),0); - scorpio::set_dof(filename,"col",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"row",dofs.size(),dofs.data()); - scorpio::set_dof(filename,"S", dofs.size(),dofs.data()); - - scorpio::eam_pio_enddef(filename); + scorpio::define_dim(filename, "n_a", ngdofs_src); + scorpio::define_dim(filename, "n_b", ngdofs_tgt); + scorpio::define_dim(filename, "n_s", nnz); + + scorpio::define_var(filename, "col", {"n_s"}, "int"); + scorpio::define_var(filename, "row", {"n_s"}, "int"); + scorpio::define_var(filename, "S", {"n_s"}, "double"); + + scorpio::enddef(filename); std::vector col(nnz), row(nnz); std::vector S(nnz); @@ -211,11 +205,11 @@ void write_map_file (const std::string& filename, const int ngdofs_src) { S[ngdofs_src+2*i+1] = 0.5; } - scorpio::grid_write_data_array(filename,"row",row.data(),nnz); - scorpio::grid_write_data_array(filename,"col",col.data(),nnz); - scorpio::grid_write_data_array(filename,"S", S.data(), nnz); + scorpio::write_var(filename,"row",row.data()); + scorpio::write_var(filename,"col",col.data()); + scorpio::write_var(filename,"S", S.data()); - scorpio::eam_pio_closefile(filename); + scorpio::release_file(filename); } TEST_CASE ("refining_remapper") { @@ -227,8 +221,7 @@ TEST_CASE ("refining_remapper") { auto engine = setup_random_test (&comm); - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); // Create a map file const int ngdofs_src = 4*comm.size(); @@ -475,7 +468,7 @@ TEST_CASE ("refining_remapper") { // Clean up r = nullptr; - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // namespace scream diff --git a/components/eamxx/src/share/tests/vertical_remapper_tests.cpp b/components/eamxx/src/share/tests/vertical_remapper_tests.cpp index 131f6380f52..f7bd62b7998 100644 --- a/components/eamxx/src/share/tests/vertical_remapper_tests.cpp +++ b/components/eamxx/src/share/tests/vertical_remapper_tests.cpp @@ -96,20 +96,14 @@ Real data_func(const int col, const int vec, const Real pres) { // Helper function to create a remap file void create_remap_file(const std::string& filename, const int nlevs, const std::vector& dofs_p, const std::vector& p_tgt) { - scorpio::register_file(filename, scorpio::FileMode::Write); + scorpio::define_dim(filename,"lev",nlevs); + scorpio::define_var(filename,"p_levs",{"lev"},"real"); + scorpio::enddef(filename); - scorpio::register_dimension(filename,"lev","lev",nlevs, false); - - scorpio::register_variable(filename,"p_levs","p_levs","none",{"lev"},"real","real","Real-lev"); - - scorpio::set_dof(filename,"p_levs",dofs_p.size(),dofs_p.data()); - - scorpio::eam_pio_enddef(filename); - - scorpio::grid_write_data_array(filename,"p_levs",p_tgt.data(),nlevs); + scorpio::write_var(filename,"p_levs",p_tgt.data()); - scorpio::eam_pio_closefile(filename); + scorpio::release_file(filename); } TEST_CASE ("vertical_remap") { @@ -121,8 +115,7 @@ TEST_CASE ("vertical_remap") { ekat::Comm comm(MPI_COMM_WORLD); - MPI_Fint fcomm = MPI_Comm_c2f(comm.mpi_comm()); - scorpio::eam_init_pio_subsystem(fcomm); + scorpio::init_subsystem(comm); // -------------------------------------- // // Set grid/map sizes // @@ -389,7 +382,7 @@ TEST_CASE ("vertical_remap") { } // Clean up scorpio stuff - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); } } // namespace scream diff --git a/components/eamxx/src/share/util/eamxx_time_interpolation.cpp b/components/eamxx/src/share/util/eamxx_time_interpolation.cpp index 3515bcdaac9..e0dba0cf838 100644 --- a/components/eamxx/src/share/util/eamxx_time_interpolation.cpp +++ b/components/eamxx/src/share/util/eamxx_time_interpolation.cpp @@ -1,4 +1,5 @@ #include "share/util/eamxx_time_interpolation.hpp" +#include "share/io/scream_io_utils.hpp" namespace scream{ namespace util { @@ -30,8 +31,8 @@ TimeInterpolation::TimeInterpolation( void TimeInterpolation::finalize() { if (m_is_data_from_file) { - m_file_data_atm_input.finalize(); - m_is_data_from_file=false; + m_file_data_atm_input = nullptr; + m_is_data_from_file = false; } } /*-----------------------------------------------------------------------------------------------*/ @@ -86,6 +87,10 @@ void TimeInterpolation::add_field(const Field& field_in, const bool store_shallo const std::string name = field_in.name(); EKAT_REQUIRE_MSG(!m_fm_time0->has_field(name) and !m_fm_time1->has_field(name), "Error!! TimeInterpolation:add_field, field + " << name << " has already been added." << "\n"); + EKAT_REQUIRE_MSG (field_in.data_type()==DataType::FloatType or field_in.data_type()==DataType::DoubleType, + "[TimeInterpolation] Error! Input field must have floating-point data type.\n" + " - field name: " + field_in.name() + "\n" + " - data type : " + e2str(field_in.data_type()) + "\n"); // Clone the field for each field manager to get all the metadata correct. auto field0 = field_in.clone(); @@ -113,7 +118,7 @@ void TimeInterpolation::shift_data() auto& field1 = m_fm_time1->get_field(name); std::swap(field0,field1); } - m_file_data_atm_input.set_field_manager(m_fm_time1); + m_file_data_atm_input->set_field_manager(m_fm_time1); } /*-----------------------------------------------------------------------------------------------*/ /* Function which will initialize the TimeStamps. @@ -160,19 +165,48 @@ void TimeInterpolation::initialize_data_from_files() ekat::ParameterList input_params; input_params.set("Field Names",m_field_names); input_params.set("Filename",triplet_curr.filename); - m_file_data_atm_input = AtmosphereInput(input_params,m_fm_time1); - m_file_data_atm_input.set_logger(m_logger); + m_file_data_atm_input = std::make_shared(input_params,m_fm_time1); + m_file_data_atm_input->set_logger(m_logger); // Assign the mask value gathered from the FillValue found in the source file. // TODO: Should we make it possible to check if FillValue is in the metadata and only assign mask_value if it is? - float var_fill_value; for (auto& name : m_field_names) { - scorpio::get_variable_metadata(triplet_curr.filename,name,"_FillValue",var_fill_value); auto& field0 = m_fm_time0->get_field(name); - field0.get_header().set_extra_data("mask_value",var_fill_value); auto& field1 = m_fm_time1->get_field(name); - field1.get_header().set_extra_data("mask_value",var_fill_value); auto& field_out = m_interp_fields.at(name); - field_out.get_header().set_extra_data("mask_value",var_fill_value); + + auto set_fill_value = [&](const auto var_fill_value) { + const auto dt = field_out.data_type(); + if (dt==DataType::FloatType) { + field0.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + field1.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + field_out.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + } else if (dt==DataType::DoubleType) { + field0.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + field1.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + field_out.get_header().set_extra_data("mask_value",static_cast(var_fill_value)); + } else { + EKAT_ERROR_MSG ( + "[TimeInterpolation] Unexpected/unsupported field data type.\n" + " - field name: " + field_out.name() + "\n" + " - data type : " + e2str(dt) + "\n"); + } + }; + + const auto& pio_var = scorpio::get_var(triplet_curr.filename,name); + if (scorpio::refine_dtype(pio_var.nc_dtype)=="float") { + auto var_fill_value = scorpio::get_attribute(triplet_curr.filename,name,"_FillValue"); + set_fill_value(var_fill_value); + } else if (scorpio::refine_dtype(pio_var.nc_dtype)=="double") { + auto var_fill_value = scorpio::get_attribute(triplet_curr.filename,name,"_FillValue"); + set_fill_value(var_fill_value); + } else { + EKAT_ERROR_MSG ( + "Unrecognized/unsupported data type\n" + " - filename: " + triplet_curr.filename + "\n" + " - varname : " + name + "\n" + " - dtype : " + pio_var.dtype + "\n"); + } + } // Read first snap of data and shift to time0 read_data(); @@ -253,10 +287,10 @@ void TimeInterpolation::set_file_data_triplets(const vos_type& list_of_files) { for (size_t ii=0; ii(time_units_tmp); + auto time_units = scorpio::get_attribute(filename,"time","units"); int time_mult; if (time_units.find("seconds") != std::string::npos) { time_mult = 1; @@ -273,10 +307,9 @@ void TimeInterpolation::set_file_data_triplets(const vos_type& list_of_files) { if (ii==0) { ts_ref = ts_file_start; } - scorpio::register_file(filename,scorpio::Read); const int ntime = scorpio::get_dimlen(filename,"time"); for (int tt=0; tt0) { ts_snap += (time_snap*time_mult); @@ -292,6 +325,7 @@ void TimeInterpolation::set_file_data_triplets(const vos_type& list_of_files) { time_idx_tmp.push_back(tt); ++running_idx; } + scorpio::release_file(filename); } // Now that we have gathered all of the timesnaps we can arrange them in order as DataFromFileTriplet objects. // Taking advantage of maps automatically self-sorting by the first arg. @@ -313,21 +347,30 @@ void TimeInterpolation::set_file_data_triplets(const vos_type& list_of_files) { void TimeInterpolation::read_data() { const auto triplet_curr = m_file_data_triplets[m_triplet_idx]; - if (triplet_curr.filename != m_file_data_atm_input.get_filename()) { + if (not m_file_data_atm_input or triplet_curr.filename != m_file_data_atm_input->get_filename()) { // Then we need to close this input stream and open a new one - m_file_data_atm_input.finalize(); ekat::ParameterList input_params; input_params.set("Field Names",m_field_names); input_params.set("Filename",triplet_curr.filename); - m_file_data_atm_input = AtmosphereInput(input_params,m_fm_time1); - m_file_data_atm_input.set_logger(m_logger); + m_file_data_atm_input = std::make_shared(input_params,m_fm_time1); + m_file_data_atm_input->set_logger(m_logger); // Also determine the FillValue, if used // TODO: Should we make it possible to check if FillValue is in the metadata and only assign mask_value if it is? - float var_fill_value; for (auto& name : m_field_names) { - scorpio::get_variable_metadata(triplet_curr.filename,name,"_FillValue",var_fill_value); auto& field = m_fm_time1->get_field(name); - field.get_header().set_extra_data("mask_value",var_fill_value); + const auto dt = field.data_type(); + if (dt==DataType::FloatType) { + auto var_fill_value = scorpio::get_attribute(triplet_curr.filename,name,"_FillValue"); + field.get_header().set_extra_data("mask_value",var_fill_value); + } else if (dt==DataType::DoubleType) { + auto var_fill_value = scorpio::get_attribute(triplet_curr.filename,name,"_FillValue"); + field.get_header().set_extra_data("mask_value",var_fill_value); + } else { + EKAT_ERROR_MSG ( + "[TimeInterpolation] Unexpected/unsupported field data type.\n" + " - field name: " + field.name() + "\n" + " - data type : " + e2str(dt) + "\n"); + } } } @@ -335,7 +378,7 @@ void TimeInterpolation::read_data() m_logger->info(m_header); m_logger->info("[EAMxx:time_interpolation] Reading data at time " + triplet_curr.timestamp.to_string()); } - m_file_data_atm_input.read_variables(triplet_curr.time_idx); + m_file_data_atm_input->read_variables(triplet_curr.time_idx); m_time1 = triplet_curr.timestamp; } /*-----------------------------------------------------------------------------------------------*/ diff --git a/components/eamxx/src/share/util/eamxx_time_interpolation.hpp b/components/eamxx/src/share/util/eamxx_time_interpolation.hpp index 870dad33013..5e5fa5112f2 100644 --- a/components/eamxx/src/share/util/eamxx_time_interpolation.hpp +++ b/components/eamxx/src/share/util/eamxx_time_interpolation.hpp @@ -89,7 +89,7 @@ class TimeInterpolation { // Variables related to the case where we use data from file std::vector m_file_data_triplets; int m_triplet_idx; - AtmosphereInput m_file_data_atm_input; + std::shared_ptr m_file_data_atm_input; bool m_is_data_from_file=false; std::shared_ptr m_logger; diff --git a/components/eamxx/tests/multi-process/physics_only/shoc_p3_nudging/create_vert_remap_and_weights.cpp b/components/eamxx/tests/multi-process/physics_only/shoc_p3_nudging/create_vert_remap_and_weights.cpp index 702d83f16d3..8cedad048a6 100644 --- a/components/eamxx/tests/multi-process/physics_only/shoc_p3_nudging/create_vert_remap_and_weights.cpp +++ b/components/eamxx/tests/multi-process/physics_only/shoc_p3_nudging/create_vert_remap_and_weights.cpp @@ -8,13 +8,10 @@ using namespace scream; void create_vert_remap() { // Simple function to create a 1D remap column to test nudging w/ remapped data - ekat::Comm io_comm(MPI_COMM_WORLD); // MPI communicator group used for I/O set as ekat object. - MPI_Fint fcomm = MPI_Comm_c2f(io_comm.mpi_comm()); // MPI communicator group used for I/O. In our simple test we use MPI_COMM_WORLD, however a subset could be used. - scorpio::eam_init_pio_subsystem(fcomm); // Gather the initial PIO subsystem data creater by component coupler + ekat::Comm comm(MPI_COMM_WORLD); + scorpio::init_subsystem(comm); int nlevs = 5*SCREAM_PACK_SIZE+1; - std::vector dofs_levs(nlevs); - std::iota(dofs_levs.begin(),dofs_levs.end(),0); std::vector p_tgt; Real p_top=0, p_bot=102500; Real dp = (p_bot - p_top) / (nlevs-1); @@ -26,24 +23,20 @@ void create_vert_remap() { std::string remap_filename = "vertical_remap.nc"; scorpio::register_file(remap_filename, scorpio::FileMode::Write); - scorpio::register_dimension(remap_filename,"lev", "lev", nlevs, false); - scorpio::register_variable(remap_filename,"p_levs","p_levs","none",{"lev"},"real","real","Real-lev"); - scorpio::set_dof(remap_filename,"p_levs",dofs_levs.size(),dofs_levs.data()); - scorpio::eam_pio_enddef(remap_filename); - scorpio::grid_write_data_array(remap_filename,"p_levs",p_tgt.data(),nlevs); - scorpio::eam_pio_closefile(remap_filename); - scorpio::eam_pio_finalize(); + scorpio::define_dim(remap_filename, "lev", nlevs); + scorpio::define_var(remap_filename,"p_levs",{"lev"},"real"); + scorpio::enddef(remap_filename); + scorpio::write_var(remap_filename,"p_levs",p_tgt.data()); + scorpio::release_file(remap_filename); + scorpio::finalize_subsystem(); } void create_nudging_weights_ncfile(int ntimes, int ncols, int nlevs, const std::string& filename) { // Simple function to create a 1D remap column to test nudging w/ remapped data - ekat::Comm io_comm(MPI_COMM_WORLD); // MPI communicator group used for I/O set as ekat object. - MPI_Fint fcomm = MPI_Comm_c2f(io_comm.mpi_comm()); // MPI communicator group used for I/O. In our simple test we use MPI_COMM_WORLD, however a subset could be used. - scorpio::eam_init_pio_subsystem(fcomm); // Gather the initial PIO subsystem data creater by component coupler + ekat::Comm comm(MPI_COMM_WORLD); + scorpio::init_subsystem(comm); - std::vector dofs(ntimes*ncols*nlevs); - std::iota(dofs.begin(),dofs.end(),0); Real plev[nlevs]; Real p_top=0, p_bot=102500; Real dp = (p_bot - p_top) / (nlevs-1); @@ -64,16 +57,18 @@ void create_nudging_weights_ncfile(int ntimes, int ncols, int nlevs, const std:: } } + // NOTE: we are generating a file with "time" dimension that is not unlimited. + // This should stress test AtmosphereInput, which will 'pretend' that + // a dimension called 'time' is unlimited, even though it isn't in the file scorpio::register_file(filename, scorpio::FileMode::Write); - scorpio::register_dimension(filename,"ncol", "ncol", ncols, false); - scorpio::register_dimension(filename,"lev", "lev", nlevs, false); - scorpio::register_dimension(filename,"time","time",ntimes, false); - scorpio::register_variable(filename,"nudging_weights","nudging_weights","none",{"lev", "ncol", "time"},"real","real","Real-lev"); - scorpio::set_dof(filename,"nudging_weights",dofs.size(),dofs.data()); - scorpio::eam_pio_enddef(filename); - scorpio::grid_write_data_array(filename,"nudging_weights",&weights[0][0][0],ntimes*ncols*nlevs); - scorpio::eam_pio_closefile(filename); - scorpio::eam_pio_finalize(); + scorpio::define_dim(filename,"ncol",ncols); + scorpio::define_dim(filename,"lev", nlevs); + scorpio::define_dim(filename,"time",ntimes); + scorpio::define_var(filename,"nudging_weights",{"time","ncol","lev"},"real"); + scorpio::enddef(filename); + scorpio::write_var(filename,"nudging_weights",&weights[0][0][0]); + scorpio::release_file(filename); + scorpio::finalize_subsystem(); } TEST_CASE("create_vert_remap_and_weights","create_vert_remap_and_weights") diff --git a/components/eamxx/tests/single-process/surface_coupling/surface_coupling.cpp b/components/eamxx/tests/single-process/surface_coupling/surface_coupling.cpp index d34059f8559..a4e2a18cbfb 100644 --- a/components/eamxx/tests/single-process/surface_coupling/surface_coupling.cpp +++ b/components/eamxx/tests/single-process/surface_coupling/surface_coupling.cpp @@ -88,7 +88,7 @@ std::vector create_from_file_test_data(const ekat::Comm& comm, cons } // Create output manager to handle the data - scorpio::eam_init_pio_subsystem(comm); + scorpio::init_subsystem(comm); ekat::ParameterList om_pl; om_pl.set("filename_prefix",std::string("surface_coupling_forcing")); om_pl.set("Field Names",fnames); @@ -119,7 +119,7 @@ std::vector create_from_file_test_data(const ekat::Comm& comm, cons // Done, finalize om.finalize(); - scorpio::eam_pio_finalize(); + scorpio::finalize_subsystem(); return om.get_list_of_files(); }