ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
mpiio.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21/** @file
22 *
23 * Concerning the file layouts.
24 * - Scalar arrays are written like this:
25 * <tt>rank0 --- rank1 --- rank2 ...</tt>
26 * where each rank dumps its scalars in the ordering of the particles.
27 * - Vector arrays are written in the rank ordering like scalar arrays.
28 * The ordering of the vector data is: <tt>v[0] v[1] v[2]</tt>, so the data
29 * looks like this:
30 * <tt>v1[0] v1[1] v1[2] v2[0] v2[1] v2[2] v3[0] ...</tt>
31 *
32 * To be able to determine the rank boundaries (a multiple of
33 * @c nlocalparts), the file 1.pref is written, which dumps the partial
34 * sum of @c nlocalparts, i.e. the prefixes in scalar arrays:
35 * - 1.prefs looks like this:
36 * <tt>0 nlocalpats_rank0 nlocalparts_rank0+nlocalparts_rank1 ...</tt>
37 *
38 * Bonds are dumped as two arrays, namely 1.bond which stores the
39 * bonding partners of the particles and 1.boff which stores the
40 * iteration indices for each particle.
41 * - 1.boff is a scalar array of size <tt>(nlocalpart + 1)</tt> per rank.
42 * - The last element (at index @c nlocalpart) of 1.boff's subpart
43 * <tt>[rank * (nlocalpart + 1) : (rank + 1) * (nlocalpart + 1)]</tt>
44 * determines the number of bonds for processor @c rank.
45 * - In this subarray one can find the bonding partners of particle
46 * <tt>id[i]</tt>. The iteration indices for local part of 1.bonds are:
47 * <tt>subarray[i] : subarray[i+1]</tt>
48 * - Take a look at the bond input code. It's easy to understand.
49 */
50
51#include "mpiio.hpp"
52
53#include "Particle.hpp"
56#include "errorhandling.hpp"
57
58#include <boost/archive/binary_iarchive.hpp>
59#include <boost/archive/binary_oarchive.hpp>
60#include <boost/iostreams/device/array.hpp>
61#include <boost/iostreams/device/back_inserter.hpp>
62#include <boost/iostreams/stream.hpp>
63
64#include <mpi.h>
65
66#include <algorithm>
67#include <cassert>
68#include <cerrno>
69#include <cstddef>
70#include <cstdio>
71#include <cstring>
72#include <sstream>
73#include <stdexcept>
74#include <string>
75#include <sys/stat.h>
76#include <tuple>
77#include <utility>
78#include <vector>
79
80namespace Mpiio {
81
82#if defined(__clang__)
83#pragma clang diagnostic push
84#pragma clang diagnostic ignored "-Wunreachable-code-return"
85#endif
86/**
87 * @brief Fatal error handler.
88 * On 1 MPI rank the error is recoverable and an exception is thrown.
89 * On more than 1 MPI rank the error is not recoverable.
90 * @param msg Custom error message
91 * @param fn File path
92 * @param extra Extra context
93 */
94static bool fatal_error(char const *msg, std::string const &fn = "",
95 std::string const &extra = "") {
96 std::stringstream what;
97 what << "MPI-IO Error: " << msg;
98 if (not fn.empty()) {
99 what << " \"" << fn << "\"";
100 }
101 if (not extra.empty()) {
102 what << " :" << extra;
103 }
104 int size;
106 if (size == 1) {
107 throw std::runtime_error(what.str());
108 }
109 fprintf(stderr, "%s\n", what.str().c_str());
110 errexit();
111 return false;
112}
113#if defined(__clang__)
114#pragma clang diagnostic pop
115#endif
116
117/**
118 * @brief Fatal error handler that closes an open file and queries the
119 * message associated with an MPI error code.
120 * On 1 MPI rank the error is recoverable and an exception is thrown.
121 * On more than 1 MPI rank the error is not recoverable.
122 * @param msg Custom error message
123 * @param fn File path
124 * @param fp File handle
125 * @param errnum MPI error code
126 */
127static bool fatal_error(char const *msg, std::string const &fn, MPI_File *fp,
128 int errnum) {
129 // get MPI error message
130 char buf[MPI_MAX_ERROR_STRING];
131 int buf_len;
133 buf[buf_len] = '\0';
134 // close file handle
135 if (fp) {
137 }
138 return fatal_error(msg, fn, buf);
139}
140
141/**
142 * @brief Dump data @p arr of size @p len starting from prefix @p pref
143 * of type @p T using @p MPI_T as MPI datatype. Beware, that @p T and
144 * @p MPI_T have to match!
145 *
146 * @param fn The file name to write to (must not already exist!)
147 * @param arr The array to dump
148 * @param len The number of elements to dump
149 * @param pref The prefix for this process
150 * @param MPI_T The MPI datatype corresponding to the template parameter @p T
151 */
152template <typename T>
153static void mpiio_dump_array(const std::string &fn, T const *arr,
154 std::size_t len, std::size_t pref,
156 MPI_File f;
157 int ret;
158 ret = MPI_File_open(MPI_COMM_WORLD, const_cast<char *>(fn.c_str()),
159 // MPI_MODE_EXCL: Prohibit overwriting
161 MPI_INFO_NULL, &f);
162 if (ret) {
163 fatal_error("Could not open file", fn, &f, ret);
164 }
165 auto const offset =
166 static_cast<MPI_Offset>(pref) * static_cast<MPI_Offset>(sizeof(T));
167 ret = MPI_File_set_view(f, offset, MPI_T, MPI_T, const_cast<char *>("native"),
169 ret |= MPI_File_write_all(f, arr, static_cast<int>(len), MPI_T,
171 static_cast<void>(ret and fatal_error("Could not write file", fn, &f, ret));
172 MPI_File_close(&f);
173}
174
175/**
176 * @brief Calculate the file offset on the local node.
177 * @param n_items Number of items on the local node.
178 * @return The number of items on all nodes with lower rank.
179 */
180static unsigned long mpi_calculate_file_offset(unsigned long n_items) {
181 unsigned long offset = 0ul;
183 return offset;
184}
185
186/**
187 * @brief Dump the fields and bond information.
188 * To be called by the head node only.
189 *
190 * @param fn The filename to write to
191 * @param fields The dumped fields
192 * @param bonded_ias The list of bonds
193 */
194static void dump_info(std::string const &fn, unsigned fields,
195 BondedInteractionsMap const &bonded_ias) {
196 // MPI-IO requires consecutive bond ids
197 auto const nbonds = bonded_ias.size();
198 assert(static_cast<std::size_t>(bonded_ias.get_next_key()) == nbonds);
199
200 FILE *f = fopen(fn.c_str(), "wb");
201 if (!f) {
202 fatal_error("Could not open file", fn);
203 }
204 std::vector<int> npartners;
205 bool success = (fwrite(&fields, sizeof(fields), 1u, f) == 1);
206 // Pack the number of partners. This is needed to interpret the bond IntList.
207 npartners.reserve(nbonds);
208 for (int bond_id = 0; bond_id < bonded_ias.get_next_key(); ++bond_id) {
209 if (bonded_ias.contains(bond_id)) {
210 npartners.emplace_back(number_of_partners(*bonded_ias.at(bond_id)));
211 }
212 }
213 success &= fwrite(&nbonds, sizeof(std::size_t), 1u, f) == 1;
214 success &= fwrite(npartners.data(), sizeof(int), nbonds, f) == nbonds;
215 fclose(f);
216 static_cast<void>(success or fatal_error("Could not write file", fn));
217}
218
219void mpi_mpiio_common_write(std::string const &prefix, unsigned fields,
220 BondedInteractionsMap const &bonded_ias,
221 ParticleRange const &particles,
223 auto const nlocalpart = static_cast<unsigned long>(particles.size());
224 auto const offset = mpi_calculate_file_offset(nlocalpart);
225 // keep buffers in order to avoid allocating them on every function call
226 auto &pos = buffers.pos;
227 auto &vel = buffers.vel;
228 auto &id = buffers.id;
229 auto &type = buffers.type;
230
231 // Realloc static buffers if necessary
232 if (nlocalpart > id.size())
233 id.resize(nlocalpart);
234 if (fields & MPIIO_OUT_POS && 3ul * nlocalpart > pos.size())
235 pos.resize(3ul * nlocalpart);
236 if (fields & MPIIO_OUT_VEL && 3ul * nlocalpart > vel.size())
237 vel.resize(3ul * nlocalpart);
238 if (fields & MPIIO_OUT_TYP && nlocalpart > type.size())
239 type.resize(nlocalpart);
240
241 // Pack the necessary information
242 auto id_it = id.begin();
243 auto type_it = type.begin();
244 auto pos_it = pos.begin();
245 auto vel_it = vel.begin();
246 for (auto const &p : particles) {
247 *id_it = p.id();
248 ++id_it;
249 if (fields & MPIIO_OUT_POS) {
250 std::copy_n(std::begin(p.pos()), 3u, pos_it);
251 pos_it += 3u;
252 }
253 if (fields & MPIIO_OUT_VEL) {
254 std::copy_n(std::begin(p.v()), 3u, vel_it);
255 vel_it += 3u;
256 }
257 if (fields & MPIIO_OUT_TYP) {
258 *type_it = p.type();
259 ++type_it;
260 }
261 }
262
263 int rank;
265 if (rank == 0)
266 dump_info(prefix + ".head", fields, bonded_ias);
267 auto const pref_offset = static_cast<unsigned long>(rank);
268 mpiio_dump_array<unsigned long>(prefix + ".pref", &offset, 1ul, pref_offset,
270 mpiio_dump_array<int>(prefix + ".id", id.data(), nlocalpart, offset, MPI_INT);
271 if (fields & MPIIO_OUT_POS)
272 mpiio_dump_array<double>(prefix + ".pos", pos.data(), 3ul * nlocalpart,
273 3ul * offset, MPI_DOUBLE);
274 if (fields & MPIIO_OUT_VEL)
275 mpiio_dump_array<double>(prefix + ".vel", vel.data(), 3ul * nlocalpart,
276 3ul * offset, MPI_DOUBLE);
277 if (fields & MPIIO_OUT_TYP)
278 mpiio_dump_array<int>(prefix + ".type", type.data(), nlocalpart, offset,
279 MPI_INT);
280
281 if (fields & MPIIO_OUT_BND) {
282 std::vector<char> bonds;
283
284 /* Construct archive that pushes back to the bond buffer */
285 {
286 namespace io = boost::iostreams;
287 io::stream_buffer<io::back_insert_device<std::vector<char>>> os{
288 io::back_inserter(bonds)};
289 boost::archive::binary_oarchive bond_archiver{os};
290
291 for (auto const &p : particles) {
292 bond_archiver << p.bonds();
293 }
294 }
295
296 // Determine the prefixes in the bond file
297 auto const bonds_size = static_cast<unsigned long>(bonds.size());
299
302 mpiio_dump_array<char>(prefix + ".bond", bonds.data(), bonds.size(),
304 }
305}
306
307/**
308 * @brief Get the number of elements in a file by its file size and @p elem_sz.
309 * I.e. query the file size using stat(2) and divide it by @p elem_sz.
310 *
311 * @param fn The filename
312 * @param elem_sz Size of a single element
313 * @return The number of elements stored in the file
314 */
315static unsigned long get_num_elem(const std::string &fn, std::size_t elem_sz) {
316 // Could also be done via MPI_File_open, MPI_File_get_size,
317 // MPI_File_close.
318 struct stat st;
319 errno = 0;
320 if (stat(fn.c_str(), &st) != 0) {
321 auto const reason = strerror(errno);
322 fatal_error("Could not get file size of", fn, reason);
323 }
324 return static_cast<unsigned long>(st.st_size) / elem_sz;
325}
326
327/**
328 * @brief Read a previously dumped array of size @p len starting from prefix
329 * @p pref of type @p T using @p MPI_T as MPI datatype. Beware, that
330 * @p T and @p MPI_T have to match!
331 *
332 * @param fn The file name to read from
333 * @param arr The array to populate
334 * @param len The number of elements to read
335 * @param pref The prefix for this process
336 * @param MPI_T The MPI datatype corresponding to the template parameter @p T
337 */
338template <typename T>
339static void mpiio_read_array(const std::string &fn, T *arr, std::size_t len,
340 std::size_t pref, MPI_Datatype MPI_T) {
341 MPI_File f;
342 int ret;
343 ret = MPI_File_open(MPI_COMM_WORLD, const_cast<char *>(fn.c_str()),
345 if (ret) {
346 fatal_error("Could not open file", fn, &f, ret);
347 }
348 auto const offset =
349 static_cast<MPI_Offset>(pref) * static_cast<MPI_Offset>(sizeof(T));
350 ret = MPI_File_set_view(f, offset, MPI_T, MPI_T, const_cast<char *>("native"),
352
353 ret |= MPI_File_read_all(f, arr, static_cast<int>(len), MPI_T,
355 static_cast<void>(ret and fatal_error("Could not read file", fn, &f, ret));
356 MPI_File_close(&f);
357}
358
359/**
360 * @brief Read the header file and return the first value.
361 * To be called by all processes.
362 *
363 * @param fn Filename of the head file
364 * @param rank The rank of the current process in @c MPI_COMM_WORLD
365 */
366static unsigned read_head(const std::string &fn, int rank) {
367 unsigned n_fields = 0u;
368 FILE *f = nullptr;
369 if (rank == 0) {
370 f = fopen(fn.c_str(), "rb");
371 static_cast<void>(not f and fatal_error("Could not open file", fn));
372 auto const n = fread(static_cast<void *>(&n_fields), sizeof n_fields, 1, f);
373 static_cast<void>((n == 1) or fatal_error("Could not read file", fn));
374 }
376 if (f) {
377 fclose(f);
378 }
379 return n_fields;
380}
381
382/**
383 * @brief Read the pref file.
384 * Needs to be called by all processes.
385 *
386 * @param fn The file name of the prefs file
387 * @param rank The rank of the current process in @c MPI_COMM_WORLD
388 * @param size The size of @c MPI_COMM_WORLD
389 * @param nglobalpart The global amount of particles
390 * @return The prefix and the local number of particles.
391 */
392static std::tuple<unsigned long, unsigned long>
393read_prefs(const std::string &fn, int rank, int size,
394 unsigned long nglobalpart) {
395 auto const pref_offset = static_cast<unsigned long>(rank);
396 unsigned long pref = 0ul;
397 unsigned long nlocalpart = 0ul;
400 if (rank > 0)
401 MPI_Send(&pref, 1, MPI_UNSIGNED_LONG, rank - 1, 0, MPI_COMM_WORLD);
402 if (rank < size - 1)
405 else
407 nlocalpart -= pref;
408 return {pref, nlocalpart};
409}
410
411void mpi_mpiio_common_read(const std::string &prefix, unsigned fields,
412 CellStructure &cell_structure) {
413 cell_structure.remove_all_particles();
414
415 int size, rank;
418 auto const nproc = get_num_elem(prefix + ".pref", sizeof(unsigned long));
419 auto const nglobalpart = get_num_elem(prefix + ".id", sizeof(int));
420
421 if (rank == 0 && nproc != static_cast<unsigned long>(size)) {
422 fatal_error("Trying to read a file with a different COMM "
423 "size than at point of writing.");
424 }
425
426 // 1.head on head node:
427 // Read head to determine fields at time of writing.
428 // Compare this var to the current fields.
429 auto const avail_fields = read_head(prefix + ".head", rank);
430 if (rank == 0 && (fields & avail_fields) != fields) {
431 fatal_error("Requesting to read fields which were not dumped.");
432 }
433
434 // 1.pref on all nodes:
435 // Read own prefix (1 int at prefix rank).
436 // Communicate own prefix to rank-1
437 // Determine nlocalpart (prefix of rank+1 - own prefix) on every node.
438 auto const [pref, nlocalpart] =
439 read_prefs(prefix + ".pref", rank, size, nglobalpart);
440
441 std::vector<Particle> particles(nlocalpart);
442
443 {
444 // 1.id on all nodes:
445 // Read nlocalpart ints at defined prefix.
446 std::vector<int> id(nlocalpart);
447 auto id_it = id.begin();
448 mpiio_read_array<int>(prefix + ".id", id.data(), nlocalpart, pref, MPI_INT);
449
450 for (auto &p : particles) {
451 p.id() = *id_it;
452 ++id_it;
453 }
454 }
455
456 if (fields & MPIIO_OUT_POS) {
457 // 1.pos on all nodes:
458 // Read nlocalpart * 3 doubles at defined prefix * 3
459 std::vector<double> pos(3ul * nlocalpart);
460 auto pos_it = pos.begin();
461 mpiio_read_array<double>(prefix + ".pos", pos.data(), 3ul * nlocalpart,
462 3ul * pref, MPI_DOUBLE);
463
464 for (auto &p : particles) {
465 std::copy_n(pos_it, 3u, std::begin(p.pos()));
466 pos_it += 3u;
467 }
468 }
469
470 if (fields & MPIIO_OUT_TYP) {
471 // 1.type on all nodes:
472 // Read nlocalpart ints at defined prefix.
473 std::vector<int> type(nlocalpart);
474 auto type_it = type.begin();
475 mpiio_read_array<int>(prefix + ".type", type.data(), nlocalpart, pref,
476 MPI_INT);
477
478 for (auto &p : particles) {
479 p.type() = *type_it;
480 ++type_it;
481 }
482 }
483
484 if (fields & MPIIO_OUT_VEL) {
485 // 1.vel on all nodes:
486 // Read nlocalpart * 3 doubles at defined prefix * 3
487 std::vector<double> vel(3ul * nlocalpart);
488 auto vel_it = vel.begin();
489 mpiio_read_array<double>(prefix + ".vel", vel.data(), 3ul * nlocalpart,
490 3ul * pref, MPI_DOUBLE);
491
492 for (auto &p : particles) {
493 std::copy_n(vel_it, 3u, std::begin(p.v()));
494 vel_it += 3u;
495 }
496 }
497
498 if (fields & MPIIO_OUT_BND) {
499 // 1.boff
500 // 1 long int per process
501 auto const pref_offset = static_cast<unsigned long>(rank);
502 unsigned long bonds_size = 0u;
506
507 // 1.bond
508 // nlocalbonds ints per process
509 std::vector<char> bond(bonds_size);
510 mpiio_read_array<char>(prefix + ".bond", bond.data(), bonds_size,
512
513 boost::iostreams::array_source src(bond.data(), bond.size());
514 boost::iostreams::stream<boost::iostreams::array_source> ss(src);
515 boost::archive::binary_iarchive ia(ss);
516
517 for (auto &p : particles) {
518 ia >> p.bonds();
519 }
520 }
521
522 for (auto &p : particles) {
523 cell_structure.add_particle(std::move(p));
524 }
525}
526} // namespace Mpiio
Data structures for bonded interactions.
int number_of_partners(Bonded_IA_Parameters const &iaparams)
Get the number of bonded partners for the specified bond.
container for bonded interactions.
bool contains(key_type const &key) const
mapped_type const & at(key_type const &key) const
Describes a cell structure / cell system.
Particle * add_particle(Particle &&p)
Add a particle.
void remove_all_particles()
Remove all particles from the cell system.
A range of particles.
base_type::size_type size() const
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
void errexit()
exit ungracefully, core dump if switched on.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
static unsigned long mpi_calculate_file_offset(unsigned long n_items)
Calculate the file offset on the local node.
Definition mpiio.cpp:180
void mpi_mpiio_common_write(std::string const &prefix, unsigned fields, BondedInteractionsMap const &bonded_ias, ParticleRange const &particles, write_buffers &buffers)
Parallel binary output using MPI-IO.
Definition mpiio.cpp:219
static std::tuple< unsigned long, unsigned long > read_prefs(const std::string &fn, int rank, int size, unsigned long nglobalpart)
Read the pref file.
Definition mpiio.cpp:393
static unsigned long get_num_elem(const std::string &fn, std::size_t elem_sz)
Get the number of elements in a file by its file size and elem_sz.
Definition mpiio.cpp:315
static void mpiio_read_array(const std::string &fn, T *arr, std::size_t len, std::size_t pref, MPI_Datatype MPI_T)
Read a previously dumped array of size len starting from prefix pref of type T using MPI_T as MPI dat...
Definition mpiio.cpp:339
static void mpiio_dump_array(const std::string &fn, T const *arr, std::size_t len, std::size_t pref, MPI_Datatype MPI_T)
Dump data arr of size len starting from prefix pref of type T using MPI_T as MPI datatype.
Definition mpiio.cpp:153
static unsigned read_head(const std::string &fn, int rank)
Read the header file and return the first value.
Definition mpiio.cpp:366
void mpi_mpiio_common_read(const std::string &prefix, unsigned fields, CellStructure &cell_structure)
Parallel binary input using MPI-IO.
Definition mpiio.cpp:411
static void dump_info(std::string const &fn, unsigned fields, BondedInteractionsMap const &bonded_ias)
Dump the fields and bond information.
Definition mpiio.cpp:194
static bool fatal_error(char const *msg, std::string const &fn="", std::string const &extra="")
Fatal error handler.
Definition mpiio.cpp:94