From ad031f7b83621d915d35553a7c14ea269c3e988d Mon Sep 17 00:00:00 2001 From: Pasquale Africa Date: Sun, 2 Jul 2023 21:18:57 +0000 Subject: [PATCH] Implement DataTransfer for disabled MPI support --- include/deal.II/grid/data_transfer.h | 912 +---------------------- include/deal.II/grid/tria.h | 3 +- source/distributed/tria.cc | 62 +- source/grid/CMakeLists.txt | 2 + source/grid/data_transfer.cc | 1029 ++++++++++++++++++++++++++ source/grid/data_transfer.inst.in | 23 + source/grid/tria.cc | 41 +- 7 files changed, 1118 insertions(+), 954 deletions(-) create mode 100644 source/grid/data_transfer.cc create mode 100644 source/grid/data_transfer.inst.in diff --git a/include/deal.II/grid/data_transfer.h b/include/deal.II/grid/data_transfer.h index 14c2b4ec7c..5eac21a8d4 100644 --- a/include/deal.II/grid/data_transfer.h +++ b/include/deal.II/grid/data_transfer.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -88,7 +89,7 @@ public: */ using cell_relation_t = typename std::pair; - DataTransfer(const MPI_Comm mpi_communicator); + DataTransfer(const MPI_Comm &mpi_communicator); /** * Prepare data transfer by calling the pack callback functions on each @@ -146,9 +147,9 @@ public: * _fixed.data for fixed size data and _variable.data * for variable size data. * - * All processors write into these files simultaneously via MPIIO. - * Each processor's position to write to will be determined - * from the provided input parameters. + * If MPI support is enabled, all processors write into these files + * simultaneously via MPIIO. Each processor's position to write to will be + * determined from the provided input parameters. * * Data has to be previously packed with pack_data(). */ @@ -168,9 +169,9 @@ public: * parameters are required to gather the memory offsets for each * callback. * - * All processors read from these files simultaneously via MPIIO. - * Each processor's position to read from will be determined - * from the provided input arguments. + * If MPI support is enabled, all processors read from these files + * simultaneously via MPIIO. Each processor's position to read from will be + * determined from the provided input arguments. * * After loading, unpack_data() needs to be called to finally * distribute data across the associated triangulation. @@ -211,14 +212,14 @@ public: /** * Consecutive buffers designed for the fixed size transfer - * functions of p4est. + * functions. */ std::vector src_data_fixed; std::vector dest_data_fixed; /** * Consecutive buffers designed for the variable size transfer - * functions of p4est. + * functions. */ std::vector src_sizes_variable; std::vector dest_sizes_variable; @@ -226,900 +227,9 @@ public: std::vector dest_data_variable; private: - MPI_Comm mpi_communicator; + const MPI_Comm &mpi_communicator; }; - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -DataTransfer::DataTransfer(const MPI_Comm mpi_communicator) - : variable_size_data_stored(false) - , mpi_communicator(mpi_communicator) -{} - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::pack_data( - const std::vector &cell_relations, - const std::vector::pack_callback_t> - &pack_callbacks_fixed, - const std::vector::pack_callback_t> - &pack_callbacks_variable) -{ - Assert(src_data_fixed.size() == 0, - ExcMessage("Previously packed data has not been released yet!")); - Assert(src_sizes_variable.size() == 0, ExcInternalError()); - - const unsigned int n_callbacks_fixed = pack_callbacks_fixed.size(); - const unsigned int n_callbacks_variable = pack_callbacks_variable.size(); - - // Store information that we packed variable size data in - // a member variable for later. - variable_size_data_stored = (n_callbacks_variable > 0); - - // If variable transfer is scheduled, we will store the data size that - // each variable size callback function writes in this auxiliary - // container. The information will be stored by each cell in this vector - // temporarily. - std::vector cell_sizes_variable_cumulative( - n_callbacks_variable); - - // Prepare the buffer structure, in which each callback function will - // store its data for each active cell. - // The outmost shell in this container construct corresponds to the - // data packed per cell. The next layer resembles the data that - // each callback function packs on the corresponding cell. These - // buffers are chains of chars stored in an std::vector. - // A visualisation of the data structure: - /* clang-format off */ - // | cell_1 | | cell_2 | ... - // || callback_1 || callback_2 |...| || callback_1 || callback_2 |...| ... - // |||char|char|...|||char|char|...|...| |||char|char|...|||char|char|...|...| ... - /* clang-format on */ - std::vector>> packed_fixed_size_data( - cell_relations.size()); - std::vector>> packed_variable_size_data( - variable_size_data_stored ? cell_relations.size() : 0); - - // - // --------- Pack data for fixed and variable size transfer --------- - // - // Iterate over all cells, call all callback functions on each cell, - // and store their data in the corresponding buffer scope. - { - auto cell_rel_it = cell_relations.cbegin(); - auto data_cell_fixed_it = packed_fixed_size_data.begin(); - auto data_cell_variable_it = packed_variable_size_data.begin(); - for (; cell_rel_it != cell_relations.cend(); ++cell_rel_it) - { - const auto &dealii_cell = cell_rel_it->first; - const auto &cell_status = cell_rel_it->second; - - // Assertions about the tree structure. - switch (cell_status) - { - case CELL_PERSIST: - case CELL_REFINE: - // double check the condition that we will only ever attach - // data to active cells when we get here - Assert(dealii_cell->is_active(), ExcInternalError()); - break; - - case CELL_COARSEN: - // double check the condition that we will only ever attach - // data to cells with children when we get here. however, we - // can only tolerate one level of coarsening at a time, so - // check that the children are all active - Assert(dealii_cell->is_active() == false, ExcInternalError()); - for (unsigned int c = 0; - c < GeometryInfo::max_children_per_cell; - ++c) - Assert(dealii_cell->child(c)->is_active(), ExcInternalError()); - break; - - case CELL_INVALID: - // do nothing on invalid cells - break; - - default: - Assert(false, ExcInternalError()); - break; - } - - // Reserve memory corresponding to the number of callback - // functions that will be called. - // If variable size transfer is scheduled, we need to leave - // room for an array that holds information about how many - // bytes each of the variable size callback functions will - // write. - // On cells flagged with CELL_INVALID, only its CellStatus - // will be stored. - const unsigned int n_fixed_size_data_sets_on_cell = - 1 + ((cell_status == CELL_INVALID) ? - 0 : - ((variable_size_data_stored ? 1 : 0) + n_callbacks_fixed)); - data_cell_fixed_it->resize(n_fixed_size_data_sets_on_cell); - - // We continue with packing all data on this specific cell. - auto data_fixed_it = data_cell_fixed_it->begin(); - - // First, we pack the CellStatus information. - // to get consistent data sizes on each cell for the fixed size - // transfer, we won't allow compression - *data_fixed_it = - Utilities::pack(cell_status, /*allow_compression=*/false); - ++data_fixed_it; - - // Proceed with all registered callback functions. - // Skip cells with the CELL_INVALID flag. - if (cell_status != CELL_INVALID) - { - // Pack fixed size data. - for (auto callback_it = pack_callbacks_fixed.cbegin(); - callback_it != pack_callbacks_fixed.cend(); - ++callback_it, ++data_fixed_it) - { - *data_fixed_it = (*callback_it)(dealii_cell, cell_status); - } - - // Pack variable size data. - // If we store variable size data, we need to transfer - // the sizes of each corresponding callback function - // via fixed size transfer as well. - if (variable_size_data_stored) - { - const unsigned int n_variable_size_data_sets_on_cell = - ((cell_status == CELL_INVALID) ? 0 : n_callbacks_variable); - data_cell_variable_it->resize( - n_variable_size_data_sets_on_cell); - - auto callback_it = pack_callbacks_variable.cbegin(); - auto data_variable_it = data_cell_variable_it->begin(); - auto sizes_variable_it = cell_sizes_variable_cumulative.begin(); - for (; callback_it != pack_callbacks_variable.cend(); - ++callback_it, ++data_variable_it, ++sizes_variable_it) - { - *data_variable_it = - (*callback_it)(dealii_cell, cell_status); - - // Store data sizes for each callback function first. - // Make it cumulative below. - *sizes_variable_it = data_variable_it->size(); - } - - // Turn size vector into its cumulative representation. - std::partial_sum(cell_sizes_variable_cumulative.begin(), - cell_sizes_variable_cumulative.end(), - cell_sizes_variable_cumulative.begin()); - - // Serialize cumulative variable size vector - // value-by-value. This way we can circumvent the overhead - // of storing the container object as a whole, since we - // know its size by the number of registered callback - // functions. - data_fixed_it->resize(n_callbacks_variable * - sizeof(unsigned int)); - for (unsigned int i = 0; i < n_callbacks_variable; ++i) - std::memcpy(&(data_fixed_it->at(i * sizeof(unsigned int))), - &(cell_sizes_variable_cumulative.at(i)), - sizeof(unsigned int)); - - ++data_fixed_it; - } - - // Double check that we packed everything we wanted - // in the fixed size buffers. - Assert(data_fixed_it == data_cell_fixed_it->end(), - ExcInternalError()); - } - - ++data_cell_fixed_it; - - // Increment the variable size data iterator - // only if we actually pack this kind of data - // to avoid getting out of bounds. - if (variable_size_data_stored) - ++data_cell_variable_it; - } // loop over cell_relations - } - - // - // ----------- Gather data sizes for fixed size transfer ------------ - // - // Generate a vector which stores the sizes of each callback function, - // including the packed CellStatus transfer. - // Find the very first cell that we wrote to with all callback - // functions (i.e. a cell that was not flagged with CELL_INVALID) - // and store the sizes of each buffer. - // - // To deal with the case that at least one of the processors does not - // own any cell at all, we will exchange the information about the data - // sizes among them later. The code in between is still well-defined, - // since the following loops will be skipped. - std::vector local_sizes_fixed( - 1 + n_callbacks_fixed + (variable_size_data_stored ? 1 : 0)); - for (const auto &data_cell : packed_fixed_size_data) - { - if (data_cell.size() == local_sizes_fixed.size()) - { - auto sizes_fixed_it = local_sizes_fixed.begin(); - auto data_fixed_it = data_cell.cbegin(); - for (; data_fixed_it != data_cell.cend(); - ++data_fixed_it, ++sizes_fixed_it) - { - *sizes_fixed_it = data_fixed_it->size(); - } - - break; - } - } - - // Check if all cells have valid sizes. - for (auto data_cell_fixed_it = packed_fixed_size_data.cbegin(); - data_cell_fixed_it != packed_fixed_size_data.cend(); - ++data_cell_fixed_it) - { - Assert((data_cell_fixed_it->size() == 1) || - (data_cell_fixed_it->size() == local_sizes_fixed.size()), - ExcInternalError()); - } - - // Share information about the packed data sizes - // of all callback functions across all processors, in case one - // of them does not own any cells at all. - std::vector global_sizes_fixed(local_sizes_fixed.size()); - Utilities::MPI::max(local_sizes_fixed, - this->mpi_communicator, - global_sizes_fixed); - - // Construct cumulative sizes, since this is the only information - // we need from now on. - sizes_fixed_cumulative.resize(global_sizes_fixed.size()); - std::partial_sum(global_sizes_fixed.begin(), - global_sizes_fixed.end(), - sizes_fixed_cumulative.begin()); - - // - // ---------- Gather data sizes for variable size transfer ---------- - // - if (variable_size_data_stored) - { - src_sizes_variable.reserve(packed_variable_size_data.size()); - for (const auto &data_cell : packed_variable_size_data) - { - int variable_data_size_on_cell = 0; - - for (const auto &data : data_cell) - variable_data_size_on_cell += data.size(); - - src_sizes_variable.push_back(variable_data_size_on_cell); - } - } - - // - // ------------------------ Build buffers --------------------------- - // - const unsigned int expected_size_fixed = - cell_relations.size() * sizes_fixed_cumulative.back(); - const unsigned int expected_size_variable = - std::accumulate(src_sizes_variable.begin(), - src_sizes_variable.end(), - std::vector::size_type(0)); - - // Move every piece of packed fixed size data into the consecutive - // buffer. - src_data_fixed.reserve(expected_size_fixed); - for (const auto &data_cell_fixed : packed_fixed_size_data) - { - // Move every fraction of packed data into the buffer - // reserved for this particular cell. - for (const auto &data_fixed : data_cell_fixed) - std::move(data_fixed.begin(), - data_fixed.end(), - std::back_inserter(src_data_fixed)); - - // If we only packed the CellStatus information - // (i.e. encountered a cell flagged CELL_INVALID), - // fill the remaining space with invalid entries. - // We can skip this if there is nothing else to pack. - if ((data_cell_fixed.size() == 1) && (sizes_fixed_cumulative.size() > 1)) - { - const std::size_t bytes_skipped = - sizes_fixed_cumulative.back() - sizes_fixed_cumulative.front(); - - src_data_fixed.insert(src_data_fixed.end(), - bytes_skipped, - static_cast(-1)); // invalid_char - } - } - - // Move every piece of packed variable size data into the consecutive - // buffer. - if (variable_size_data_stored) - { - src_data_variable.reserve(expected_size_variable); - for (const auto &data_cell : packed_variable_size_data) - { - // Move every fraction of packed data into the buffer - // reserved for this particular cell. - for (const auto &data : data_cell) - std::move(data.begin(), - data.end(), - std::back_inserter(src_data_variable)); - } - } - - // Double check that we packed everything correctly. - Assert(src_data_fixed.size() == expected_size_fixed, ExcInternalError()); - Assert(src_data_variable.size() == expected_size_variable, - ExcInternalError()); -} - - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::unpack_cell_status( - std::vector::cell_relation_t> - &cell_relations) const -{ - Assert(sizes_fixed_cumulative.size() > 0, - ExcMessage("No data has been packed!")); - if (cell_relations.size() > 0) - { - Assert(dest_data_fixed.size() > 0, - ExcMessage("No data has been received!")); - } - - // Size of CellStatus object that will be unpacked on each cell. - const unsigned int size = sizes_fixed_cumulative.front(); - - // Iterate over all cells and overwrite the CellStatus - // information from the transferred data. - // Proceed buffer iterator position to next cell after - // each iteration. - auto cell_rel_it = cell_relations.begin(); - auto dest_fixed_it = dest_data_fixed.cbegin(); - for (; cell_rel_it != cell_relations.end(); - ++cell_rel_it, dest_fixed_it += sizes_fixed_cumulative.back()) - { - cell_rel_it->second = // cell_status - Utilities::unpack(dest_fixed_it, - dest_fixed_it + size, - /*allow_compression=*/false); - } -} - - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::unpack_data( - const std::vector::cell_relation_t> - & cell_relations, - const unsigned int handle, - const std::function< - void(const cell_iterator &, - const CellStatus &, - const boost::iterator_range::const_iterator> &)> - &unpack_callback) const -{ - // We decode the handle returned by register_data_attach() back into - // a format we can use. All even handles belong to those callback - // functions which write/read variable size data, all odd handles - // interact with fixed size buffers. - const bool callback_variable_transfer = (handle % 2 == 0); - const unsigned int callback_index = handle / 2; - - // Cells will always receive fixed size data (i.e., CellStatus - // information), but not necessarily variable size data (e.g., with a - // ParticleHandler a cell might not contain any particle at all). - // Thus it is sufficient to check if fixed size data has been received. - Assert(sizes_fixed_cumulative.size() > 0, - ExcMessage("No data has been packed!")); - if (cell_relations.size() > 0) - { - Assert(dest_data_fixed.size() > 0, - ExcMessage("No data has been received!")); - } - - std::vector::const_iterator dest_data_it; - std::vector::const_iterator dest_sizes_cell_it; - - // Depending on whether our callback function unpacks fixed or - // variable size data, we have to pursue different approaches - // to localize the correct fraction of the buffer from which - // we are allowed to read. - unsigned int offset = numbers::invalid_unsigned_int; - unsigned int size = numbers::invalid_unsigned_int; - unsigned int data_increment = numbers::invalid_unsigned_int; - - if (callback_variable_transfer) - { - // For the variable size data, we need to extract the - // data size from the fixed size buffer on each cell. - // - // We packed this information last, so the last packed - // object in the fixed size buffer corresponds to the - // variable data sizes. - // - // The last entry of sizes_fixed_cumulative corresponds - // to the size of all fixed size data packed on the cell. - // To get the offset for the last packed object, we need - // to get the next-to-last entry. - const unsigned int offset_variable_data_sizes = - sizes_fixed_cumulative[sizes_fixed_cumulative.size() - 2]; - - // This iterator points to the data size that the - // callback_function packed for each specific cell. - // Adjust buffer iterator to the offset of the callback - // function so that we only have to advance its position - // to the next cell after each iteration. - dest_sizes_cell_it = dest_data_fixed.cbegin() + - offset_variable_data_sizes + - callback_index * sizeof(unsigned int); - - // Let the data iterator point to the correct buffer. - dest_data_it = dest_data_variable.cbegin(); - } - else - { - // For the fixed size data, we can get the information about - // the buffer location on each cell directly from the - // sizes_fixed_cumulative vector. - offset = sizes_fixed_cumulative[callback_index]; - size = sizes_fixed_cumulative[callback_index + 1] - offset; - data_increment = sizes_fixed_cumulative.back(); - - // Let the data iterator point to the correct buffer. - // Adjust buffer iterator to the offset of the callback - // function so that we only have to advance its position - // to the next cell after each iteration. - if (cell_relations.begin() != cell_relations.end()) - dest_data_it = dest_data_fixed.cbegin() + offset; - } - - // Iterate over all cells and unpack the transferred data. - auto cell_rel_it = cell_relations.begin(); - auto dest_sizes_it = dest_sizes_variable.cbegin(); - for (; cell_rel_it != cell_relations.end(); ++cell_rel_it) - { - const auto &dealii_cell = cell_rel_it->first; - const auto &cell_status = cell_rel_it->second; - - if (callback_variable_transfer) - { - // Update the increment according to the whole data size - // of the current cell. - data_increment = *dest_sizes_it; - - if (cell_status != CELL_INVALID) - { - // Extract the corresponding values for offset and size from - // the cumulative sizes array stored in the fixed size - // buffer. - if (callback_index == 0) - offset = 0; - else - std::memcpy(&offset, - &(*(dest_sizes_cell_it - sizeof(unsigned int))), - sizeof(unsigned int)); - - std::memcpy(&size, &(*dest_sizes_cell_it), sizeof(unsigned int)); - - size -= offset; - - // Move the data iterator to the corresponding position - // of the callback function and adjust the increment - // accordingly. - dest_data_it += offset; - data_increment -= offset; - } - - // Advance data size iterators to the next cell, avoid iterating - // past the end of dest_sizes_cell_it - if (cell_rel_it != cell_relations.end() - 1) - dest_sizes_cell_it += sizes_fixed_cumulative.back(); - ++dest_sizes_it; - } - - switch (cell_status) - { - case CELL_PERSIST: - case CELL_COARSEN: - unpack_callback(dealii_cell, - cell_status, - boost::make_iterator_range(dest_data_it, - dest_data_it + size)); - break; - - case CELL_REFINE: - unpack_callback(dealii_cell->parent(), - cell_status, - boost::make_iterator_range(dest_data_it, - dest_data_it + size)); - break; - - case CELL_INVALID: - // Skip this cell. - break; - - default: - Assert(false, ExcInternalError()); - break; - } - - if (cell_rel_it != cell_relations.end() - 1) - dest_data_it += data_increment; - } -} - - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::save(const unsigned int global_first_cell, - const unsigned int global_num_cells, - const std::string &filename) const -{ -#ifdef DEAL_II_WITH_MPI - // Large fractions of this function have been copied from - // DataOutInterface::write_vtu_in_parallel. - // TODO: Write general MPIIO interface. - - Assert(sizes_fixed_cumulative.size() > 0, - ExcMessage("No data has been packed!")); - - const int myrank = Utilities::MPI::this_mpi_process(mpi_communicator); - - const unsigned int bytes_per_cell = sizes_fixed_cumulative.back(); - - // - // ---------- Fixed size data ---------- - // - { - const std::string fname_fixed = std::string(filename) + "_fixed.data"; - - MPI_Info info; - int ierr = MPI_Info_create(&info); - AssertThrowMPI(ierr); - - MPI_File fh; - ierr = MPI_File_open(mpi_communicator, - fname_fixed.c_str(), - MPI_MODE_CREATE | MPI_MODE_WRONLY, - info, - &fh); - AssertThrowMPI(ierr); - - ierr = MPI_File_set_size(fh, 0); // delete the file contents - AssertThrowMPI(ierr); - // this barrier is necessary, because otherwise others might already - // write while one core is still setting the size to zero. - ierr = MPI_Barrier(mpi_communicator); - AssertThrowMPI(ierr); - ierr = MPI_Info_free(&info); - AssertThrowMPI(ierr); - // ------------------ - - // Write cumulative sizes to file. - // Since each processor owns the same information about the data - // sizes, it is sufficient to let only the first processor perform - // this task. - if (myrank == 0) - { - ierr = Utilities::MPI::LargeCount::File_write_at_c( - fh, - 0, - sizes_fixed_cumulative.data(), - sizes_fixed_cumulative.size(), - MPI_UNSIGNED, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - } - - // Write packed data to file simultaneously. - const MPI_Offset size_header = - sizes_fixed_cumulative.size() * sizeof(unsigned int); - - // Make sure we do the following computation in 64bit integers to be - // able to handle 4GB+ files: - const MPI_Offset my_global_file_position = - size_header + static_cast(global_first_cell) * bytes_per_cell; - - ierr = Utilities::MPI::LargeCount::File_write_at_c(fh, - my_global_file_position, - src_data_fixed.data(), - src_data_fixed.size(), - MPI_BYTE, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - ierr = MPI_File_close(&fh); - AssertThrowMPI(ierr); - } - - - - // - // ---------- Variable size data ---------- - // - if (variable_size_data_stored) - { - const std::string fname_variable = - std::string(filename) + "_variable.data"; - - MPI_Info info; - int ierr = MPI_Info_create(&info); - AssertThrowMPI(ierr); - - MPI_File fh; - ierr = MPI_File_open(mpi_communicator, - fname_variable.c_str(), - MPI_MODE_CREATE | MPI_MODE_WRONLY, - info, - &fh); - AssertThrowMPI(ierr); - - ierr = MPI_File_set_size(fh, 0); // delete the file contents - AssertThrowMPI(ierr); - // this barrier is necessary, because otherwise others might already - // write while one core is still setting the size to zero. - ierr = MPI_Barrier(mpi_communicator); - AssertThrowMPI(ierr); - ierr = MPI_Info_free(&info); - AssertThrowMPI(ierr); - - // Write sizes of each cell into file simultaneously. - { - const MPI_Offset my_global_file_position = - static_cast(global_first_cell) * sizeof(unsigned int); - - // It is very unlikely that a single process has more than - // 2 billion cells, but we might as well check. - AssertThrow(src_sizes_variable.size() < - static_cast(std::numeric_limits::max()), - ExcNotImplemented()); - - ierr = - Utilities::MPI::LargeCount::File_write_at_c(fh, - my_global_file_position, - src_sizes_variable.data(), - src_sizes_variable.size(), - MPI_INT, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - } - - // Gather size of data in bytes we want to store from this - // processor and compute the prefix sum. We do this in 64 bit - // to avoid overflow for files larger than 4GB: - const std::uint64_t size_on_proc = src_data_variable.size(); - std::uint64_t prefix_sum = 0; - ierr = MPI_Exscan( - &size_on_proc, &prefix_sum, 1, MPI_UINT64_T, MPI_SUM, mpi_communicator); - AssertThrowMPI(ierr); - - const MPI_Offset my_global_file_position = - static_cast(global_num_cells) * sizeof(unsigned int) + - prefix_sum; - - // Write data consecutively into file. - ierr = - Utilities::MPI::LargeCount::File_write_at_c(fh, - my_global_file_position, - src_data_variable.data(), - src_data_variable.size(), - MPI_BYTE, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - - ierr = MPI_File_close(&fh); - AssertThrowMPI(ierr); - } -#else - (void)global_first_cell; - (void)global_num_cells; - (void)filename; - - AssertThrow(false, ExcNeedsMPI()); -#endif -} - - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::load( - const unsigned int global_first_cell, - const unsigned int global_num_cells, - const unsigned int local_num_cells, - const std::string &filename, - const unsigned int n_attached_deserialize_fixed, - const unsigned int n_attached_deserialize_variable) -{ -#ifdef DEAL_II_WITH_MPI - // Large fractions of this function have been copied from - // DataOutInterface::write_vtu_in_parallel. - // TODO: Write general MPIIO interface. - - Assert(dest_data_fixed.size() == 0, - ExcMessage("Previously loaded data has not been released yet!")); - - variable_size_data_stored = (n_attached_deserialize_variable > 0); - - // - // ---------- Fixed size data ---------- - // - { - const std::string fname_fixed = std::string(filename) + "_fixed.data"; - - MPI_Info info; - int ierr = MPI_Info_create(&info); - AssertThrowMPI(ierr); - - MPI_File fh; - ierr = MPI_File_open( - mpi_communicator, fname_fixed.c_str(), MPI_MODE_RDONLY, info, &fh); - AssertThrowMPI(ierr); - - ierr = MPI_Info_free(&info); - AssertThrowMPI(ierr); - - // Read cumulative sizes from file. - // Since all processors need the same information about the data - // sizes, let each of them retrieve it by reading from the same - // location in the file. - sizes_fixed_cumulative.resize(1 + n_attached_deserialize_fixed + - (variable_size_data_stored ? 1 : 0)); - ierr = - Utilities::MPI::LargeCount::File_read_at_c(fh, - 0, - sizes_fixed_cumulative.data(), - sizes_fixed_cumulative.size(), - MPI_UNSIGNED, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - // Allocate sufficient memory. - const unsigned int bytes_per_cell = sizes_fixed_cumulative.back(); - dest_data_fixed.resize(static_cast(local_num_cells) * - bytes_per_cell); - - // Read packed data from file simultaneously. - const MPI_Offset size_header = - sizes_fixed_cumulative.size() * sizeof(unsigned int); - - // Make sure we do the following computation in 64bit integers to be - // able to handle 4GB+ files: - const MPI_Offset my_global_file_position = - size_header + static_cast(global_first_cell) * bytes_per_cell; - - ierr = Utilities::MPI::LargeCount::File_read_at_c(fh, - my_global_file_position, - dest_data_fixed.data(), - dest_data_fixed.size(), - MPI_BYTE, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - - ierr = MPI_File_close(&fh); - AssertThrowMPI(ierr); - } - - // - // ---------- Variable size data ---------- - // - if (variable_size_data_stored) - { - const std::string fname_variable = - std::string(filename) + "_variable.data"; - - MPI_Info info; - int ierr = MPI_Info_create(&info); - AssertThrowMPI(ierr); - - MPI_File fh; - ierr = MPI_File_open( - mpi_communicator, fname_variable.c_str(), MPI_MODE_RDONLY, info, &fh); - AssertThrowMPI(ierr); - - ierr = MPI_Info_free(&info); - AssertThrowMPI(ierr); - - // Read sizes of all locally owned cells. - dest_sizes_variable.resize(local_num_cells); - - const MPI_Offset my_global_file_position_sizes = - static_cast(global_first_cell) * sizeof(unsigned int); - - ierr = Utilities::MPI::LargeCount::File_read_at_c( - fh, - my_global_file_position_sizes, - dest_sizes_variable.data(), - dest_sizes_variable.size(), - MPI_INT, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - - // Compute my data size in bytes and compute prefix sum. We do this - // in 64 bit to avoid overflow for files larger than 4 GB: - const std::uint64_t size_on_proc = - std::accumulate(dest_sizes_variable.begin(), - dest_sizes_variable.end(), - 0ULL); - - std::uint64_t prefix_sum = 0; - ierr = MPI_Exscan( - &size_on_proc, &prefix_sum, 1, MPI_UINT64_T, MPI_SUM, mpi_communicator); - AssertThrowMPI(ierr); - - const MPI_Offset my_global_file_position = - static_cast(global_num_cells) * sizeof(unsigned int) + - prefix_sum; - - dest_data_variable.resize(size_on_proc); - - ierr = - Utilities::MPI::LargeCount::File_read_at_c(fh, - my_global_file_position, - dest_data_variable.data(), - dest_data_variable.size(), - MPI_BYTE, - MPI_STATUS_IGNORE); - AssertThrowMPI(ierr); - - ierr = MPI_File_close(&fh); - AssertThrowMPI(ierr); - } -#else - (void)global_first_cell; - (void)global_num_cells; - (void)local_num_cells; - (void)filename; - (void)n_attached_deserialize_fixed; - (void)n_attached_deserialize_variable; - - AssertThrow(false, ExcNeedsMPI()); -#endif -} - - -template -DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) -void DataTransfer::clear() -{ - variable_size_data_stored = false; - - // free information about data sizes - sizes_fixed_cumulative.clear(); - sizes_fixed_cumulative.shrink_to_fit(); - - // free fixed size transfer data - src_data_fixed.clear(); - src_data_fixed.shrink_to_fit(); - - dest_data_fixed.clear(); - dest_data_fixed.shrink_to_fit(); - - // free variable size transfer data - src_sizes_variable.clear(); - src_sizes_variable.shrink_to_fit(); - - src_data_variable.clear(); - src_data_variable.shrink_to_fit(); - - dest_sizes_variable.clear(); - dest_sizes_variable.shrink_to_fit(); - - dest_data_variable.clear(); - dest_data_variable.shrink_to_fit(); -} - DEAL_II_NAMESPACE_CLOSE #endif diff --git a/include/deal.II/grid/tria.h b/include/deal.II/grid/tria.h index 81bc4a19a5..a828d99bb0 100644 --- a/include/deal.II/grid/tria.h +++ b/include/deal.II/grid/tria.h @@ -2033,6 +2033,7 @@ public: * @{ */ + using CellStatus = ::dealii::CellStatus; /** * A structure used to accumulate the results of the `weight` signal slot @@ -3685,7 +3686,7 @@ protected: std::vector::cell_relation_t> local_cell_relations; - std::unique_ptr> data_transfer; + DataTransfer data_transfer; /** * @} */ diff --git a/source/distributed/tria.cc b/source/distributed/tria.cc index 9640ba8d0d..0d36591536 100644 --- a/source/distributed/tria.cc +++ b/source/distributed/tria.cc @@ -1868,13 +1868,13 @@ namespace parallel const typename dealii::internal::p4est::types::gloidx *previous_global_first_quadrant) { - Assert(this->data_transfer->sizes_fixed_cumulative.size() > 0, + Assert(this->data_transfer.sizes_fixed_cumulative.size() > 0, ExcMessage("No data has been packed!")); // Resize memory according to the data that we will receive. - this->data_transfer->dest_data_fixed.resize( + this->data_transfer.dest_data_fixed.resize( parallel_forest->local_num_quadrants * - this->data_transfer->sizes_fixed_cumulative.back()); + this->data_transfer.sizes_fixed_cumulative.back()); // Execute non-blocking fixed size transfer. typename dealii::internal::p4est::types::transfer_context @@ -1885,14 +1885,14 @@ namespace parallel previous_global_first_quadrant, parallel_forest->mpicomm, 0, - this->data_transfer->dest_data_fixed.data(), - this->data_transfer->src_data_fixed.data(), - this->data_transfer->sizes_fixed_cumulative.back()); + this->data_transfer.dest_data_fixed.data(), + this->data_transfer.src_data_fixed.data(), + this->data_transfer.sizes_fixed_cumulative.back()); - if (this->data_transfer->variable_size_data_stored) + if (this->data_transfer.variable_size_data_stored) { // Resize memory according to the data that we will receive. - this->data_transfer->dest_sizes_variable.resize( + this->data_transfer.dest_sizes_variable.resize( parallel_forest->local_num_quadrants); // Execute fixed size transfer of data sizes for variable size @@ -1902,23 +1902,23 @@ namespace parallel previous_global_first_quadrant, parallel_forest->mpicomm, 1, - this->data_transfer->dest_sizes_variable.data(), - this->data_transfer->src_sizes_variable.data(), + this->data_transfer.dest_sizes_variable.data(), + this->data_transfer.src_sizes_variable.data(), sizeof(unsigned int)); } dealii::internal::p4est::functions::transfer_fixed_end(tf_context); // Release memory of previously packed data. - this->data_transfer->src_data_fixed.clear(); - this->data_transfer->src_data_fixed.shrink_to_fit(); + this->data_transfer.src_data_fixed.clear(); + this->data_transfer.src_data_fixed.shrink_to_fit(); - if (this->data_transfer->variable_size_data_stored) + if (this->data_transfer.variable_size_data_stored) { // Resize memory according to the data that we will receive. - this->data_transfer->dest_data_variable.resize( - std::accumulate(this->data_transfer->dest_sizes_variable.begin(), - this->data_transfer->dest_sizes_variable.end(), + this->data_transfer.dest_data_variable.resize( + std::accumulate(this->data_transfer.dest_sizes_variable.begin(), + this->data_transfer.dest_sizes_variable.end(), std::vector::size_type(0))); # if DEAL_II_P4EST_VERSION_GTE(2, 0, 65, 0) @@ -1928,10 +1928,10 @@ namespace parallel // at all, which is mandatory if one of our processes does not own // any quadrant. This bypasses the assertion from being triggered. // - see: https://github.com/cburstedde/p4est/issues/48 - if (this->data_transfer->src_sizes_variable.size() == 0) - this->data_transfer->src_sizes_variable.resize(1); - if (this->data_transfer->dest_sizes_variable.size() == 0) - this->data_transfer->dest_sizes_variable.resize(1); + if (this->data_transfer.src_sizes_variable.size() == 0) + this->data_transfer.src_sizes_variable.resize(1); + if (this->data_transfer.dest_sizes_variable.size() == 0) + this->data_transfer.dest_sizes_variable.resize(1); # endif // Execute variable size transfer. @@ -1940,16 +1940,16 @@ namespace parallel previous_global_first_quadrant, parallel_forest->mpicomm, 1, - this->data_transfer->dest_data_variable.data(), - this->data_transfer->dest_sizes_variable.data(), - this->data_transfer->src_data_variable.data(), - this->data_transfer->src_sizes_variable.data()); + this->data_transfer.dest_data_variable.data(), + this->data_transfer.dest_sizes_variable.data(), + this->data_transfer.src_data_variable.data(), + this->data_transfer.src_sizes_variable.data()); // Release memory of previously packed data. - this->data_transfer->src_sizes_variable.clear(); - this->data_transfer->src_sizes_variable.shrink_to_fit(); - this->data_transfer->src_data_variable.clear(); - this->data_transfer->src_data_variable.shrink_to_fit(); + this->data_transfer.src_sizes_variable.clear(); + this->data_transfer.src_sizes_variable.shrink_to_fit(); + this->data_transfer.src_data_variable.clear(); + this->data_transfer.src_data_variable.shrink_to_fit(); } } @@ -3385,7 +3385,7 @@ namespace parallel // pack data before triangulation gets updated if (this->cell_attached_data.n_attached_data_sets > 0) { - this->data_transfer->pack_data( + this->data_transfer.pack_data( this->local_cell_relations, this->cell_attached_data.pack_callbacks_fixed, this->cell_attached_data.pack_callbacks_variable); @@ -3418,7 +3418,7 @@ namespace parallel previous_global_first_quadrant.data()); // also update the CellStatus information on the new mesh - this->data_transfer->unpack_cell_status(this->local_cell_relations); + this->data_transfer.unpack_cell_status(this->local_cell_relations); } # ifdef DEBUG @@ -3558,7 +3558,7 @@ namespace parallel // pack data before triangulation gets updated if (this->cell_attached_data.n_attached_data_sets > 0) { - this->data_transfer->pack_data( + this->data_transfer.pack_data( this->local_cell_relations, this->cell_attached_data.pack_callbacks_fixed, this->cell_attached_data.pack_callbacks_variable); diff --git a/source/grid/CMakeLists.txt b/source/grid/CMakeLists.txt index 3d70ba8095..fb2c6554b6 100644 --- a/source/grid/CMakeLists.txt +++ b/source/grid/CMakeLists.txt @@ -32,6 +32,7 @@ endif() set(_unity_include_src cell_id.cc + data_transfer.cc grid_refinement.cc intergrid_map.cc manifold.cc @@ -70,6 +71,7 @@ setup_source_list("${_unity_include_src}" set(_inst cell_id.inst.in + data_transfer.inst.in grid_generator_cgal.inst.in grid_generator_from_name.inst.in grid_generator.inst.in diff --git a/source/grid/data_transfer.cc b/source/grid/data_transfer.cc new file mode 100644 index 0000000000..0f9c6c8a18 --- /dev/null +++ b/source/grid/data_transfer.cc @@ -0,0 +1,1029 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 1998 - 2023 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + +#include + +#include + +#include +#include + +#include +#include + +DEAL_II_NAMESPACE_OPEN + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +DataTransfer::DataTransfer(const MPI_Comm &mpi_communicator) + : variable_size_data_stored(false) + , mpi_communicator(mpi_communicator) +{} + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::pack_data( + const std::vector &cell_relations, + const std::vector::pack_callback_t> + &pack_callbacks_fixed, + const std::vector::pack_callback_t> + &pack_callbacks_variable) +{ + Assert(src_data_fixed.size() == 0, + ExcMessage("Previously packed data has not been released yet!")); + Assert(src_sizes_variable.size() == 0, ExcInternalError()); + + const unsigned int n_callbacks_fixed = pack_callbacks_fixed.size(); + const unsigned int n_callbacks_variable = pack_callbacks_variable.size(); + + // Store information that we packed variable size data in + // a member variable for later. + variable_size_data_stored = (n_callbacks_variable > 0); + + // If variable transfer is scheduled, we will store the data size that + // each variable size callback function writes in this auxiliary + // container. The information will be stored by each cell in this vector + // temporarily. + std::vector cell_sizes_variable_cumulative( + n_callbacks_variable); + + // Prepare the buffer structure, in which each callback function will + // store its data for each active cell. + // The outmost shell in this container construct corresponds to the + // data packed per cell. The next layer resembles the data that + // each callback function packs on the corresponding cell. These + // buffers are chains of chars stored in an std::vector. + // A visualisation of the data structure: + /* clang-format off */ + // | cell_1 | | cell_2 | ... + // || callback_1 || callback_2 |...| || callback_1 || callback_2 |...| ... + // |||char|char|...|||char|char|...|...| |||char|char|...|||char|char|...|...| ... + /* clang-format on */ + std::vector>> packed_fixed_size_data( + cell_relations.size()); + std::vector>> packed_variable_size_data( + variable_size_data_stored ? cell_relations.size() : 0); + + // + // --------- Pack data for fixed and variable size transfer --------- + // + // Iterate over all cells, call all callback functions on each cell, + // and store their data in the corresponding buffer scope. + { + auto cell_rel_it = cell_relations.cbegin(); + auto data_cell_fixed_it = packed_fixed_size_data.begin(); + auto data_cell_variable_it = packed_variable_size_data.begin(); + for (; cell_rel_it != cell_relations.cend(); ++cell_rel_it) + { + const auto &dealii_cell = cell_rel_it->first; + const auto &cell_status = cell_rel_it->second; + + // Assertions about the tree structure. + switch (cell_status) + { + case CELL_PERSIST: + case CELL_REFINE: + // double check the condition that we will only ever attach + // data to active cells when we get here + Assert(dealii_cell->is_active(), ExcInternalError()); + break; + + case CELL_COARSEN: + // double check the condition that we will only ever attach + // data to cells with children when we get here. however, we + // can only tolerate one level of coarsening at a time, so + // check that the children are all active + Assert(dealii_cell->is_active() == false, ExcInternalError()); + for (unsigned int c = 0; + c < GeometryInfo::max_children_per_cell; + ++c) + Assert(dealii_cell->child(c)->is_active(), ExcInternalError()); + break; + + case CELL_INVALID: + // do nothing on invalid cells + break; + + default: + Assert(false, ExcInternalError()); + break; + } + + // Reserve memory corresponding to the number of callback + // functions that will be called. + // If variable size transfer is scheduled, we need to leave + // room for an array that holds information about how many + // bytes each of the variable size callback functions will + // write. + // On cells flagged with CELL_INVALID, only its CellStatus + // will be stored. + const unsigned int n_fixed_size_data_sets_on_cell = + 1 + ((cell_status == CELL_INVALID) ? + 0 : + ((variable_size_data_stored ? 1 : 0) + n_callbacks_fixed)); + data_cell_fixed_it->resize(n_fixed_size_data_sets_on_cell); + + // We continue with packing all data on this specific cell. + auto data_fixed_it = data_cell_fixed_it->begin(); + + // First, we pack the CellStatus information. + // to get consistent data sizes on each cell for the fixed size + // transfer, we won't allow compression + *data_fixed_it = + Utilities::pack(cell_status, /*allow_compression=*/false); + ++data_fixed_it; + + // Proceed with all registered callback functions. + // Skip cells with the CELL_INVALID flag. + if (cell_status != CELL_INVALID) + { + // Pack fixed size data. + for (auto callback_it = pack_callbacks_fixed.cbegin(); + callback_it != pack_callbacks_fixed.cend(); + ++callback_it, ++data_fixed_it) + { + *data_fixed_it = (*callback_it)(dealii_cell, cell_status); + } + + // Pack variable size data. + // If we store variable size data, we need to transfer + // the sizes of each corresponding callback function + // via fixed size transfer as well. + if (variable_size_data_stored) + { + const unsigned int n_variable_size_data_sets_on_cell = + ((cell_status == CELL_INVALID) ? 0 : n_callbacks_variable); + data_cell_variable_it->resize( + n_variable_size_data_sets_on_cell); + + auto callback_it = pack_callbacks_variable.cbegin(); + auto data_variable_it = data_cell_variable_it->begin(); + auto sizes_variable_it = cell_sizes_variable_cumulative.begin(); + for (; callback_it != pack_callbacks_variable.cend(); + ++callback_it, ++data_variable_it, ++sizes_variable_it) + { + *data_variable_it = + (*callback_it)(dealii_cell, cell_status); + + // Store data sizes for each callback function first. + // Make it cumulative below. + *sizes_variable_it = data_variable_it->size(); + } + + // Turn size vector into its cumulative representation. + std::partial_sum(cell_sizes_variable_cumulative.begin(), + cell_sizes_variable_cumulative.end(), + cell_sizes_variable_cumulative.begin()); + + // Serialize cumulative variable size vector + // value-by-value. This way we can circumvent the overhead + // of storing the container object as a whole, since we + // know its size by the number of registered callback + // functions. + data_fixed_it->resize(n_callbacks_variable * + sizeof(unsigned int)); + for (unsigned int i = 0; i < n_callbacks_variable; ++i) + std::memcpy(&(data_fixed_it->at(i * sizeof(unsigned int))), + &(cell_sizes_variable_cumulative.at(i)), + sizeof(unsigned int)); + + ++data_fixed_it; + } + + // Double check that we packed everything we wanted + // in the fixed size buffers. + Assert(data_fixed_it == data_cell_fixed_it->end(), + ExcInternalError()); + } + + ++data_cell_fixed_it; + + // Increment the variable size data iterator + // only if we actually pack this kind of data + // to avoid getting out of bounds. + if (variable_size_data_stored) + ++data_cell_variable_it; + } // loop over cell_relations + } + + // + // ----------- Gather data sizes for fixed size transfer ------------ + // + // Generate a vector which stores the sizes of each callback function, + // including the packed CellStatus transfer. + // Find the very first cell that we wrote to with all callback + // functions (i.e. a cell that was not flagged with CELL_INVALID) + // and store the sizes of each buffer. + // + // To deal with the case that at least one of the processors does not + // own any cell at all, we will exchange the information about the data + // sizes among them later. The code in between is still well-defined, + // since the following loops will be skipped. + std::vector local_sizes_fixed( + 1 + n_callbacks_fixed + (variable_size_data_stored ? 1 : 0)); + for (const auto &data_cell : packed_fixed_size_data) + { + if (data_cell.size() == local_sizes_fixed.size()) + { + auto sizes_fixed_it = local_sizes_fixed.begin(); + auto data_fixed_it = data_cell.cbegin(); + for (; data_fixed_it != data_cell.cend(); + ++data_fixed_it, ++sizes_fixed_it) + { + *sizes_fixed_it = data_fixed_it->size(); + } + + break; + } + } + + // Check if all cells have valid sizes. + for (auto data_cell_fixed_it = packed_fixed_size_data.cbegin(); + data_cell_fixed_it != packed_fixed_size_data.cend(); + ++data_cell_fixed_it) + { + Assert((data_cell_fixed_it->size() == 1) || + (data_cell_fixed_it->size() == local_sizes_fixed.size()), + ExcInternalError()); + } + + // Share information about the packed data sizes + // of all callback functions across all processors, in case one + // of them does not own any cells at all. + std::vector global_sizes_fixed(local_sizes_fixed.size()); + Utilities::MPI::max(local_sizes_fixed, + this->mpi_communicator, + global_sizes_fixed); + + // Construct cumulative sizes, since this is the only information + // we need from now on. + sizes_fixed_cumulative.resize(global_sizes_fixed.size()); + std::partial_sum(global_sizes_fixed.begin(), + global_sizes_fixed.end(), + sizes_fixed_cumulative.begin()); + + // + // ---------- Gather data sizes for variable size transfer ---------- + // + if (variable_size_data_stored) + { + src_sizes_variable.reserve(packed_variable_size_data.size()); + for (const auto &data_cell : packed_variable_size_data) + { + int variable_data_size_on_cell = 0; + + for (const auto &data : data_cell) + variable_data_size_on_cell += data.size(); + + src_sizes_variable.push_back(variable_data_size_on_cell); + } + } + + // + // ------------------------ Build buffers --------------------------- + // + const unsigned int expected_size_fixed = + cell_relations.size() * sizes_fixed_cumulative.back(); + const unsigned int expected_size_variable = + std::accumulate(src_sizes_variable.begin(), + src_sizes_variable.end(), + std::vector::size_type(0)); + + // Move every piece of packed fixed size data into the consecutive + // buffer. + src_data_fixed.reserve(expected_size_fixed); + for (const auto &data_cell_fixed : packed_fixed_size_data) + { + // Move every fraction of packed data into the buffer + // reserved for this particular cell. + for (const auto &data_fixed : data_cell_fixed) + std::move(data_fixed.begin(), + data_fixed.end(), + std::back_inserter(src_data_fixed)); + + // If we only packed the CellStatus information + // (i.e. encountered a cell flagged CELL_INVALID), + // fill the remaining space with invalid entries. + // We can skip this if there is nothing else to pack. + if ((data_cell_fixed.size() == 1) && (sizes_fixed_cumulative.size() > 1)) + { + const std::size_t bytes_skipped = + sizes_fixed_cumulative.back() - sizes_fixed_cumulative.front(); + + src_data_fixed.insert(src_data_fixed.end(), + bytes_skipped, + static_cast(-1)); // invalid_char + } + } + + // Move every piece of packed variable size data into the consecutive + // buffer. + if (variable_size_data_stored) + { + src_data_variable.reserve(expected_size_variable); + for (const auto &data_cell : packed_variable_size_data) + { + // Move every fraction of packed data into the buffer + // reserved for this particular cell. + for (const auto &data : data_cell) + std::move(data.begin(), + data.end(), + std::back_inserter(src_data_variable)); + } + } + + // Double check that we packed everything correctly. + Assert(src_data_fixed.size() == expected_size_fixed, ExcInternalError()); + Assert(src_data_variable.size() == expected_size_variable, + ExcInternalError()); +} + + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::unpack_cell_status( + std::vector::cell_relation_t> + &cell_relations) const +{ + Assert(sizes_fixed_cumulative.size() > 0, + ExcMessage("No data has been packed!")); + if (cell_relations.size() > 0) + { + Assert(dest_data_fixed.size() > 0, + ExcMessage("No data has been received!")); + } + + // Size of CellStatus object that will be unpacked on each cell. + const unsigned int size = sizes_fixed_cumulative.front(); + + // Iterate over all cells and overwrite the CellStatus + // information from the transferred data. + // Proceed buffer iterator position to next cell after + // each iteration. + auto cell_rel_it = cell_relations.begin(); + auto dest_fixed_it = dest_data_fixed.cbegin(); + for (; cell_rel_it != cell_relations.end(); + ++cell_rel_it, dest_fixed_it += sizes_fixed_cumulative.back()) + { + cell_rel_it->second = // cell_status + Utilities::unpack(dest_fixed_it, + dest_fixed_it + size, + /*allow_compression=*/false); + } +} + + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::unpack_data( + const std::vector::cell_relation_t> + & cell_relations, + const unsigned int handle, + const std::function< + void(const cell_iterator &, + const CellStatus &, + const boost::iterator_range::const_iterator> &)> + &unpack_callback) const +{ + // We decode the handle returned by register_data_attach() back into + // a format we can use. All even handles belong to those callback + // functions which write/read variable size data, all odd handles + // interact with fixed size buffers. + const bool callback_variable_transfer = (handle % 2 == 0); + const unsigned int callback_index = handle / 2; + + // Cells will always receive fixed size data (i.e., CellStatus + // information), but not necessarily variable size data (e.g., with a + // ParticleHandler a cell might not contain any particle at all). + // Thus it is sufficient to check if fixed size data has been received. + Assert(sizes_fixed_cumulative.size() > 0, + ExcMessage("No data has been packed!")); + if (cell_relations.size() > 0) + { + Assert(dest_data_fixed.size() > 0, + ExcMessage("No data has been received!")); + } + + std::vector::const_iterator dest_data_it; + std::vector::const_iterator dest_sizes_cell_it; + + // Depending on whether our callback function unpacks fixed or + // variable size data, we have to pursue different approaches + // to localize the correct fraction of the buffer from which + // we are allowed to read. + unsigned int offset = numbers::invalid_unsigned_int; + unsigned int size = numbers::invalid_unsigned_int; + unsigned int data_increment = numbers::invalid_unsigned_int; + + if (callback_variable_transfer) + { + // For the variable size data, we need to extract the + // data size from the fixed size buffer on each cell. + // + // We packed this information last, so the last packed + // object in the fixed size buffer corresponds to the + // variable data sizes. + // + // The last entry of sizes_fixed_cumulative corresponds + // to the size of all fixed size data packed on the cell. + // To get the offset for the last packed object, we need + // to get the next-to-last entry. + const unsigned int offset_variable_data_sizes = + sizes_fixed_cumulative[sizes_fixed_cumulative.size() - 2]; + + // This iterator points to the data size that the + // callback_function packed for each specific cell. + // Adjust buffer iterator to the offset of the callback + // function so that we only have to advance its position + // to the next cell after each iteration. + dest_sizes_cell_it = dest_data_fixed.cbegin() + + offset_variable_data_sizes + + callback_index * sizeof(unsigned int); + + // Let the data iterator point to the correct buffer. + dest_data_it = dest_data_variable.cbegin(); + } + else + { + // For the fixed size data, we can get the information about + // the buffer location on each cell directly from the + // sizes_fixed_cumulative vector. + offset = sizes_fixed_cumulative[callback_index]; + size = sizes_fixed_cumulative[callback_index + 1] - offset; + data_increment = sizes_fixed_cumulative.back(); + + // Let the data iterator point to the correct buffer. + // Adjust buffer iterator to the offset of the callback + // function so that we only have to advance its position + // to the next cell after each iteration. + if (cell_relations.begin() != cell_relations.end()) + dest_data_it = dest_data_fixed.cbegin() + offset; + } + + // Iterate over all cells and unpack the transferred data. + auto cell_rel_it = cell_relations.begin(); + auto dest_sizes_it = dest_sizes_variable.cbegin(); + for (; cell_rel_it != cell_relations.end(); ++cell_rel_it) + { + const auto &dealii_cell = cell_rel_it->first; + const auto &cell_status = cell_rel_it->second; + + if (callback_variable_transfer) + { + // Update the increment according to the whole data size + // of the current cell. + data_increment = *dest_sizes_it; + + if (cell_status != CELL_INVALID) + { + // Extract the corresponding values for offset and size from + // the cumulative sizes array stored in the fixed size + // buffer. + if (callback_index == 0) + offset = 0; + else + std::memcpy(&offset, + &(*(dest_sizes_cell_it - sizeof(unsigned int))), + sizeof(unsigned int)); + + std::memcpy(&size, &(*dest_sizes_cell_it), sizeof(unsigned int)); + + size -= offset; + + // Move the data iterator to the corresponding position + // of the callback function and adjust the increment + // accordingly. + dest_data_it += offset; + data_increment -= offset; + } + + // Advance data size iterators to the next cell, avoid iterating + // past the end of dest_sizes_cell_it + if (cell_rel_it != cell_relations.end() - 1) + dest_sizes_cell_it += sizes_fixed_cumulative.back(); + ++dest_sizes_it; + } + + switch (cell_status) + { + case CELL_PERSIST: + case CELL_COARSEN: + unpack_callback(dealii_cell, + cell_status, + boost::make_iterator_range(dest_data_it, + dest_data_it + size)); + break; + + case CELL_REFINE: + unpack_callback(dealii_cell->parent(), + cell_status, + boost::make_iterator_range(dest_data_it, + dest_data_it + size)); + break; + + case CELL_INVALID: + // Skip this cell. + break; + + default: + Assert(false, ExcInternalError()); + break; + } + + if (cell_rel_it != cell_relations.end() - 1) + dest_data_it += data_increment; + } +} + + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::save(const unsigned int global_first_cell, + const unsigned int global_num_cells, + const std::string &filename) const +{ + Assert(sizes_fixed_cumulative.size() > 0, + ExcMessage("No data has been packed!")); + +#ifdef DEAL_II_WITH_MPI + // Large fractions of this function have been copied from + // DataOutInterface::write_vtu_in_parallel. + // TODO: Write general MPIIO interface. + + const int myrank = Utilities::MPI::this_mpi_process(mpi_communicator); + const int mpisize = Utilities::MPI::n_mpi_processes(mpi_communicator); + + if (mpisize > 1) + { + const unsigned int bytes_per_cell = sizes_fixed_cumulative.back(); + + // + // ---------- Fixed size data ---------- + // + { + const std::string fname_fixed = std::string(filename) + "_fixed.data"; + + MPI_Info info; + int ierr = MPI_Info_create(&info); + AssertThrowMPI(ierr); + + MPI_File fh; + ierr = MPI_File_open(mpi_communicator, + fname_fixed.c_str(), + MPI_MODE_CREATE | MPI_MODE_WRONLY, + info, + &fh); + AssertThrowMPI(ierr); + + ierr = MPI_File_set_size(fh, 0); // delete the file contents + AssertThrowMPI(ierr); + // this barrier is necessary, because otherwise others might already + // write while one core is still setting the size to zero. + ierr = MPI_Barrier(mpi_communicator); + AssertThrowMPI(ierr); + ierr = MPI_Info_free(&info); + AssertThrowMPI(ierr); + // ------------------ + + // Write cumulative sizes to file. + // Since each processor owns the same information about the data + // sizes, it is sufficient to let only the first processor perform + // this task. + if (myrank == 0) + { + ierr = Utilities::MPI::LargeCount::File_write_at_c( + fh, + 0, + sizes_fixed_cumulative.data(), + sizes_fixed_cumulative.size(), + MPI_UNSIGNED, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + } + + // Write packed data to file simultaneously. + const MPI_Offset size_header = + sizes_fixed_cumulative.size() * sizeof(unsigned int); + + // Make sure we do the following computation in 64bit integers to be + // able to handle 4GB+ files: + const MPI_Offset my_global_file_position = + size_header + + static_cast(global_first_cell) * bytes_per_cell; + + ierr = + Utilities::MPI::LargeCount::File_write_at_c(fh, + my_global_file_position, + src_data_fixed.data(), + src_data_fixed.size(), + MPI_BYTE, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + ierr = MPI_File_close(&fh); + AssertThrowMPI(ierr); + } + + + + // + // ---------- Variable size data ---------- + // + if (variable_size_data_stored) + { + const std::string fname_variable = + std::string(filename) + "_variable.data"; + + MPI_Info info; + int ierr = MPI_Info_create(&info); + AssertThrowMPI(ierr); + + MPI_File fh; + ierr = MPI_File_open(mpi_communicator, + fname_variable.c_str(), + MPI_MODE_CREATE | MPI_MODE_WRONLY, + info, + &fh); + AssertThrowMPI(ierr); + + ierr = MPI_File_set_size(fh, 0); // delete the file contents + AssertThrowMPI(ierr); + // this barrier is necessary, because otherwise others might already + // write while one core is still setting the size to zero. + ierr = MPI_Barrier(mpi_communicator); + AssertThrowMPI(ierr); + ierr = MPI_Info_free(&info); + AssertThrowMPI(ierr); + + // Write sizes of each cell into file simultaneously. + { + const MPI_Offset my_global_file_position = + static_cast(global_first_cell) * sizeof(unsigned int); + + // It is very unlikely that a single process has more than + // 2 billion cells, but we might as well check. + AssertThrow(src_sizes_variable.size() < + static_cast( + std::numeric_limits::max()), + ExcNotImplemented()); + + ierr = Utilities::MPI::LargeCount::File_write_at_c( + fh, + my_global_file_position, + src_sizes_variable.data(), + src_sizes_variable.size(), + MPI_INT, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + } + + // Gather size of data in bytes we want to store from this + // processor and compute the prefix sum. We do this in 64 bit + // to avoid overflow for files larger than 4GB: + const std::uint64_t size_on_proc = src_data_variable.size(); + std::uint64_t prefix_sum = 0; + ierr = MPI_Exscan(&size_on_proc, + &prefix_sum, + 1, + MPI_UINT64_T, + MPI_SUM, + mpi_communicator); + AssertThrowMPI(ierr); + + const MPI_Offset my_global_file_position = + static_cast(global_num_cells) * sizeof(unsigned int) + + prefix_sum; + + // Write data consecutively into file. + ierr = Utilities::MPI::LargeCount::File_write_at_c( + fh, + my_global_file_position, + src_data_variable.data(), + src_data_variable.size(), + MPI_BYTE, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + + ierr = MPI_File_close(&fh); + AssertThrowMPI(ierr); + } + } // if (mpisize > 1) + else +#endif + { + (void)global_first_cell; + (void)global_num_cells; + + // + // ---------- Fixed size data ---------- + // + { + const std::string fname_fixed = std::string(filename) + "_fixed.data"; + + std::ofstream file(fname_fixed, std::ios::binary | std::ios::out); + + // Write header data. + file.write(reinterpret_cast( + sizes_fixed_cumulative.data()), + sizes_fixed_cumulative.size() * sizeof(unsigned int)); + + // Write packed data. + file.write(reinterpret_cast(src_data_fixed.data()), + src_data_fixed.size() * sizeof(char)); + + file.close(); + } + + // + // ---------- Variable size data ---------- + // + if (variable_size_data_stored) + { + const std::string fname_variable = + std::string(filename) + "_variable.data"; + + std::ofstream file(fname_variable, std::ios::binary | std::ios::out); + + // Write header data. + file.write(reinterpret_cast(src_sizes_variable.data()), + src_sizes_variable.size() * sizeof(int)); + + // Write packed data. + file.write(reinterpret_cast(src_data_variable.data()), + src_data_variable.size() * sizeof(char)); + + file.close(); + } + } +} + + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::load( + const unsigned int global_first_cell, + const unsigned int global_num_cells, + const unsigned int local_num_cells, + const std::string &filename, + const unsigned int n_attached_deserialize_fixed, + const unsigned int n_attached_deserialize_variable) +{ + Assert(dest_data_fixed.size() == 0, + ExcMessage("Previously loaded data has not been released yet!")); + + variable_size_data_stored = (n_attached_deserialize_variable > 0); + +#ifdef DEAL_II_WITH_MPI + // Large fractions of this function have been copied from + // DataOutInterface::write_vtu_in_parallel. + // TODO: Write general MPIIO interface. + + const int mpisize = Utilities::MPI::n_mpi_processes(mpi_communicator); + + if (mpisize > 1) + { + // + // ---------- Fixed size data ---------- + // + { + const std::string fname_fixed = std::string(filename) + "_fixed.data"; + + MPI_Info info; + int ierr = MPI_Info_create(&info); + AssertThrowMPI(ierr); + + MPI_File fh; + ierr = MPI_File_open( + mpi_communicator, fname_fixed.c_str(), MPI_MODE_RDONLY, info, &fh); + AssertThrowMPI(ierr); + + ierr = MPI_Info_free(&info); + AssertThrowMPI(ierr); + + // Read cumulative sizes from file. + // Since all processors need the same information about the data + // sizes, let each of them retrieve it by reading from the same + // location in the file. + sizes_fixed_cumulative.resize(1 + n_attached_deserialize_fixed + + (variable_size_data_stored ? 1 : 0)); + ierr = Utilities::MPI::LargeCount::File_read_at_c( + fh, + 0, + sizes_fixed_cumulative.data(), + sizes_fixed_cumulative.size(), + MPI_UNSIGNED, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + // Allocate sufficient memory. + const unsigned int bytes_per_cell = sizes_fixed_cumulative.back(); + dest_data_fixed.resize(static_cast(local_num_cells) * + bytes_per_cell); + + // Read packed data from file simultaneously. + const MPI_Offset size_header = + sizes_fixed_cumulative.size() * sizeof(unsigned int); + + // Make sure we do the following computation in 64bit integers to be + // able to handle 4GB+ files: + const MPI_Offset my_global_file_position = + size_header + + static_cast(global_first_cell) * bytes_per_cell; + + ierr = + Utilities::MPI::LargeCount::File_read_at_c(fh, + my_global_file_position, + dest_data_fixed.data(), + dest_data_fixed.size(), + MPI_BYTE, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + + ierr = MPI_File_close(&fh); + AssertThrowMPI(ierr); + } + + // + // ---------- Variable size data ---------- + // + if (variable_size_data_stored) + { + const std::string fname_variable = + std::string(filename) + "_variable.data"; + + MPI_Info info; + int ierr = MPI_Info_create(&info); + AssertThrowMPI(ierr); + + MPI_File fh; + ierr = MPI_File_open(mpi_communicator, + fname_variable.c_str(), + MPI_MODE_RDONLY, + info, + &fh); + AssertThrowMPI(ierr); + + ierr = MPI_Info_free(&info); + AssertThrowMPI(ierr); + + // Read sizes of all locally owned cells. + dest_sizes_variable.resize(local_num_cells); + + const MPI_Offset my_global_file_position_sizes = + static_cast(global_first_cell) * sizeof(unsigned int); + + ierr = Utilities::MPI::LargeCount::File_read_at_c( + fh, + my_global_file_position_sizes, + dest_sizes_variable.data(), + dest_sizes_variable.size(), + MPI_INT, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + + // Compute my data size in bytes and compute prefix sum. We do this + // in 64 bit to avoid overflow for files larger than 4 GB: + const std::uint64_t size_on_proc = + std::accumulate(dest_sizes_variable.begin(), + dest_sizes_variable.end(), + 0ULL); + + std::uint64_t prefix_sum = 0; + ierr = MPI_Exscan(&size_on_proc, + &prefix_sum, + 1, + MPI_UINT64_T, + MPI_SUM, + mpi_communicator); + AssertThrowMPI(ierr); + + const MPI_Offset my_global_file_position = + static_cast(global_num_cells) * sizeof(unsigned int) + + prefix_sum; + + dest_data_variable.resize(size_on_proc); + + ierr = Utilities::MPI::LargeCount::File_read_at_c( + fh, + my_global_file_position, + dest_data_variable.data(), + dest_data_variable.size(), + MPI_BYTE, + MPI_STATUS_IGNORE); + AssertThrowMPI(ierr); + + ierr = MPI_File_close(&fh); + AssertThrowMPI(ierr); + } + } + else // if (mpisize > 1) +#endif + { + (void)global_first_cell; + (void)global_num_cells; + (void)local_num_cells; + + // + // ---------- Fixed size data ---------- + // + { + const std::string fname_fixed = std::string(filename) + "_fixed.data"; + + std::ifstream file(fname_fixed, std::ios::binary | std::ios::in); + sizes_fixed_cumulative.resize(1 + n_attached_deserialize_fixed + + (variable_size_data_stored ? 1 : 0)); + + // Read header data. + file.read(reinterpret_cast(sizes_fixed_cumulative.data()), + sizes_fixed_cumulative.size() * sizeof(unsigned int)); + + const unsigned int bytes_per_cell = sizes_fixed_cumulative.back(); + dest_data_fixed.resize(static_cast(local_num_cells) * + bytes_per_cell); + + // Read packed data. + file.read(reinterpret_cast(dest_data_fixed.data()), + dest_data_fixed.size() * sizeof(char)); + + file.close(); + } + + // + // ---------- Variable size data ---------- + // + if (variable_size_data_stored) + { + const std::string fname_variable = + std::string(filename) + "_variable.data"; + + std::ifstream file(fname_variable, std::ios::binary | std::ios::in); + + // Read header data. + file.read(reinterpret_cast(dest_sizes_variable.data()), + dest_sizes_variable.size() * sizeof(int)); + + // Read packed data. + file.read(reinterpret_cast(dest_data_variable.data()), + dest_data_variable.size() * sizeof(char)); + + file.close(); + } + } +} + + +template +DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) +void DataTransfer::clear() +{ + variable_size_data_stored = false; + + // free information about data sizes + sizes_fixed_cumulative.clear(); + sizes_fixed_cumulative.shrink_to_fit(); + + // free fixed size transfer data + src_data_fixed.clear(); + src_data_fixed.shrink_to_fit(); + + dest_data_fixed.clear(); + dest_data_fixed.shrink_to_fit(); + + // free variable size transfer data + src_sizes_variable.clear(); + src_sizes_variable.shrink_to_fit(); + + src_data_variable.clear(); + src_data_variable.shrink_to_fit(); + + dest_sizes_variable.clear(); + dest_sizes_variable.shrink_to_fit(); + + dest_data_variable.clear(); + dest_data_variable.shrink_to_fit(); +} + +// explicit instantiations +#include "data_transfer.inst" + +DEAL_II_NAMESPACE_CLOSE diff --git a/source/grid/data_transfer.inst.in b/source/grid/data_transfer.inst.in new file mode 100644 index 0000000000..d4eb339a87 --- /dev/null +++ b/source/grid/data_transfer.inst.in @@ -0,0 +1,23 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2010 - 2023 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + + + +for (deal_II_dimension : DIMENSIONS; deal_II_space_dimension : SPACE_DIMENSIONS) + { +#if deal_II_dimension <= deal_II_space_dimension + template class DataTransfer; +#endif + } diff --git a/source/grid/tria.cc b/source/grid/tria.cc index 22695f336f..21fcd7fa43 100644 --- a/source/grid/tria.cc +++ b/source/grid/tria.cc @@ -11064,7 +11064,9 @@ DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) Triangulation::Triangulation( const MeshSmoothing smooth_grid, const bool check_for_distorted_cells) - : smooth_grid(smooth_grid) + : cell_attached_data({0, 0, {}, {}}) + , data_transfer(get_communicator()) + , smooth_grid(smooth_grid) , anisotropic_refinement(false) , check_for_distorted_cells(check_for_distorted_cells) { @@ -11081,10 +11083,6 @@ Triangulation::Triangulation( signals.post_refinement.connect(signals.any_change); signals.clear.connect(signals.any_change); signals.mesh_movement.connect(signals.any_change); - - cell_attached_data = {0, 0, {}, {}}; - data_transfer = - std::make_unique>(get_communicator()); } @@ -11094,6 +11092,7 @@ DEAL_II_CXX20_REQUIRES((concepts::is_valid_dim_spacedim)) Triangulation::Triangulation( Triangulation &&tria) noexcept : Subscriptor(std::move(tria)) + , data_transfer(get_communicator()) , smooth_grid(tria.smooth_grid) , reference_cells(std::move(tria.reference_cells)) , periodic_face_pairs_level_0(std::move(tria.periodic_face_pairs_level_0)) @@ -11190,7 +11189,7 @@ void Triangulation::clear() reference_cells.clear(); cell_attached_data = {0, 0, {}, {}}; - data_transfer->clear(); + data_transfer.clear(); } @@ -14864,9 +14863,9 @@ void Triangulation::notify_ready_to_unpack( &unpack_callback) { // perform unpacking - this->data_transfer->unpack_data(this->local_cell_relations, - handle, - unpack_callback); + this->data_transfer.unpack_data(this->local_cell_relations, + handle, + unpack_callback); // decrease counters --this->cell_attached_data.n_attached_data_sets; @@ -14886,7 +14885,7 @@ void Triangulation::notify_ready_to_unpack( // everybody got their data, time for cleanup! this->cell_attached_data.pack_callbacks_fixed.clear(); this->cell_attached_data.pack_callbacks_variable.clear(); - this->data_transfer->clear(); + this->data_transfer.clear(); // reset all cell_status entries after coarsening/refinement for (auto &cell_rel : this->local_cell_relations) @@ -14909,16 +14908,16 @@ void Triangulation::save_attached_data( if (this->cell_attached_data.n_attached_data_sets > 0) { // pack attached data first - tria->data_transfer->pack_data( + tria->data_transfer.pack_data( tria->local_cell_relations, tria->cell_attached_data.pack_callbacks_fixed, tria->cell_attached_data.pack_callbacks_variable); // then store buffers in file - tria->data_transfer->save(global_first_cell, global_num_cells, filename); + tria->data_transfer.save(global_first_cell, global_num_cells, filename); // and release the memory afterwards - tria->data_transfer->clear(); + tria->data_transfer.clear(); } // clear all of the callback data, as explained in the documentation of @@ -14944,14 +14943,14 @@ void Triangulation::load_attached_data( // load saved data, if any was stored if (this->cell_attached_data.n_attached_deserialize > 0) { - this->data_transfer->load(global_first_cell, - global_num_cells, - local_num_cells, - filename, - n_attached_deserialize_fixed, - n_attached_deserialize_variable); - - this->data_transfer->unpack_cell_status(this->local_cell_relations); + this->data_transfer.load(global_first_cell, + global_num_cells, + local_num_cells, + filename, + n_attached_deserialize_fixed, + n_attached_deserialize_variable); + + this->data_transfer.unpack_cell_status(this->local_cell_relations); // the CellStatus of all stored cells should always be CELL_PERSIST. for (const auto &cell_rel : this->local_cell_relations) -- 2.39.5