69static std::unordered_map<std::string, H5MDOutputFields>
const fields_map = {
87 for (
auto const &field_name : fields) {
89 throw std::invalid_argument(
"Unknown field '" + field_name +
"'");
97 std::filesystem::path
const &to) {
102 auto constexpr option_fail_if_exists = std::filesystem::copy_options::none;
104 std::filesystem::copy_file(from, to, option_fail_if_exists);
105 }
catch (std::filesystem::filesystem_error
const &) {
110template <
typename extent_type>
112 extent_type
const &change_extent) {
113 auto extents = dataset.getSpace().getDimensions();
114 auto const rank = extents.size();
116 for (
auto i = 0u; i < rank; i++) {
117 extents[i] += change_extent[i];
119 dataset.resize(extents);
122template <
typename value_type,
typename extent_type>
123static void write_dataset(value_type
const &data, HighFive::DataSet &dataset,
124 extent_type
const &offset, extent_type
const &count) {
125 auto xfer_props = HighFive::DataTransferProps{};
126 xfer_props.add(HighFive::UseCollectiveIO{});
128 dataset.select(offset, count).write(data, xfer_props);
131template <
typename value_type,
typename extent_type>
132static void write_dataset(value_type
const &data, HighFive::DataSet &dataset,
133 extent_type
const &change_extent,
134 extent_type
const &offset, extent_type
const &count) {
141 std::filesystem::path
const &script_path) {
142 if (!script_path.empty()) {
143 std::ifstream scriptfile(script_path);
144 std::string buffer(std::istreambuf_iterator<char>(scriptfile),
145 std::istreambuf_iterator<char>{});
146 h5md_file.createGroup(
"/parameters");
147 auto group = h5md_file.createGroup(
"/parameters/files");
148 group.createAttribute(
"script", buffer);
153void File::init_file() {
154 auto const file_exists = std::filesystem::exists(m_file_path);
155 auto const backup_file_exists = std::filesystem::exists(m_backup_path);
167 if (m_comm.rank() == 0)
171 throw incompatible_h5mdfile();
174 if (backup_file_exists)
175 throw left_backupfile();
180void File::load_datasets() {
181 auto &datasets = *m_datasets;
182 for (
auto const &ds : m_h5md_specification.get_datasets()) {
185 auto path = ds.path();
186 datasets[path] = m_h5md_file->getDataSet(path);
190void File::create_groups() {
191 for (
auto const &ds : m_h5md_specification.get_datasets()) {
192 std::stringstream ss(ds.group);
194 std::string current_path =
"/";
195 while (std::getline(ss, segment,
'/')) {
198 current_path +=
"/" + segment;
199 if (!m_h5md_file->exist(current_path)) {
200 m_h5md_file->createGroup(current_path);
206static std::vector<std::size_t>
create_dims(hsize_t rank, hsize_t data_dim) {
208 return {0ul, 0ul, data_dim};
211 return {0ul, data_dim};
220 return {max_dim, max_dim, data_dim};
223 return {max_dim, max_dim};
231 auto const chunk_size = (rank > 1ul) ? size : hsize_t{1ul};
233 return {1ul, chunk_size, data_dim};
236 return {1ul, chunk_size};
242void File::create_datasets() {
243 auto &datasets = *m_datasets;
244 for (
auto const &ds : m_h5md_specification.get_datasets()) {
248 auto maxdims =
create_maxdims(ds.rank, ds.data_dim, H5S_UNLIMITED);
249 auto dataspace = HighFive::DataSpace(dims, maxdims);
250 auto const chunk_size =
static_cast<hsize_t
>(m_chunk_size);
252 HighFive::DataSetCreateProps props;
253 props.add(HighFive::Chunking(chunk));
254 auto path = ds.path();
255 if (ds.type == H5T_NATIVE_INT) {
256 datasets.emplace(path,
257 m_h5md_file->createDataSet<
int>(path, dataspace, props));
258 }
else if (ds.type == H5T_NATIVE_DOUBLE) {
260 path, m_h5md_file->createDataSet<
double>(path, dataspace, props));
265void File::load_file() {
266 HighFive::FileAccessProps fapl;
267 fapl.add(HighFive::MPIOFileAccess{m_comm, MPI_INFO_NULL});
268 fapl.add(HighFive::MPIOCollectiveMetadata{});
269 m_h5md_file = std::make_unique<HighFive::File>(
270 m_file_path.string(), HighFive::File::ReadWrite, fapl);
275 auto h5md_group = h5md_file.createGroup(
"h5md");
276 auto att = h5md_group.createAttribute<std::size_t>(
278 HighFive::DataSpace::From(std::array<std::size_t, 2>{{1ul, 1ul}}));
279 att.write(std::array<std::size_t, 2>{{1ul, 1ul}});
280 auto h5md_creator_group = h5md_group.createGroup(
"creator");
281 h5md_creator_group.createAttribute(
"name",
"ESPResSo");
282 h5md_creator_group.createAttribute(
"version", ESPRESSO_VERSION);
283 auto h5md_author_group = h5md_group.createGroup(
"author");
284 h5md_author_group.createAttribute(
"name",
"N/A");
285 auto box_path =
"/particles/atoms/box";
286 if (h5md_file.exist(box_path)) {
287 auto group = h5md_file.getGroup(box_path);
288 group.createAttribute(
"dimension", 3);
289 group.createAttribute(
"boundary",
"periodic");
293void File::write_units() {
294 auto &datasets = *m_datasets;
296 datasets.at(
"/particles/atoms/mass/value")
300 datasets.at(
"/particles/atoms/charge/value")
304 datasets.at(
"/particles/atoms/position/value")
306 datasets.at(
"/particles/atoms/box/edges/value")
310 datasets.at(
"/particles/atoms/lees_edwards/offset/value")
314 datasets.at(
"/particles/atoms/velocity/value")
318 datasets.at(
"/particles/atoms/force/value")
322 datasets.at(
"/particles/atoms/id/time")
327void File::create_hard_links() {
328 std::string path_step =
"/particles/atoms/id/step";
329 std::string path_time =
"/particles/atoms/id/time";
330 for (
auto &ds : m_h5md_specification.get_datasets()) {
332 char const *from =
nullptr;
333 if (ds.name ==
"step") {
334 from = path_step.c_str();
335 }
else if (ds.name ==
"time") {
336 from = path_time.c_str();
338 assert(from !=
nullptr);
339 if (H5Lcreate_hard(m_h5md_file->getId(), from, m_h5md_file->getId(),
340 ds.path().c_str(), H5P_DEFAULT, H5P_DEFAULT) < 0) {
341 throw std::runtime_error(
"Error creating hard link for " + ds.path());
347void File::create_file() {
348 HighFive::FileAccessProps fapl;
349 fapl.add(HighFive::MPIOFileAccess{m_comm, MPI_INFO_NULL});
350 fapl.add(HighFive::MPIOCollectiveMetadata{});
351 m_h5md_file = std::make_unique<HighFive::File>(m_file_path.string(),
352 HighFive::File::Create, fapl);
362 if (m_comm.rank() == 0) {
363 std::filesystem::remove(m_backup_path);
369template <std::
size_t rank>
struct slice_info {};
371template <>
struct slice_info<3> {
372 static auto extent(hsize_t n_part_diff) {
373 return Vector3s{1ul, n_part_diff, 0ul};
375 static constexpr auto count(std::size_t local_n_part) {
376 return Vector3s{1ul, local_n_part, 3ul};
378 static auto offset(hsize_t n_time_steps, hsize_t prefix) {
379 return Vector3s{n_time_steps, prefix, 0ul};
381 template <
typename T>
382 static boost::multi_array<T, 3> reshape(std::vector<T>
const &v1d,
385 boost::multi_array<T, 3> data(boost::extents[0][0][0]);
388 auto const rows = count[1];
389 auto const cols = count[2];
391 boost::multi_array<T, 3> data(
392 boost::extents[1][
static_cast<long>(rows)][
static_cast<long>(cols)]);
394 for (std::size_t i = 0; i < rows; ++i) {
395 for (std::size_t j = 0; j < cols; ++j) {
396 data[0][i][j] = v1d[cols * i + j];
404template <>
struct slice_info<2> {
405 static auto extent(hsize_t n_part_diff) {
return Vector2s{1ul, n_part_diff}; }
406 static constexpr auto count(std::size_t local_n) {
409 static auto offset(hsize_t n_time_steps, hsize_t prefix) {
410 return Vector2s{n_time_steps, prefix};
412 template <
typename T>
413 static boost::multi_array<T, 2> reshape(std::vector<T>
const &v1d,
416 boost::multi_array<T, 2> data(boost::extents[0][0]);
419 auto const cols = count[1];
421 boost::multi_array<T, 2> data(boost::extents[1][
static_cast<long>(cols)]);
423 for (std::size_t i = 0; i < cols; ++i) {
431template <
typename T>
struct get_buffer_traits {};
434 requires std::is_arithmetic_v<T>
435struct get_buffer_traits<T> {
437 constexpr static std::size_t dim = 1ul;
440template <
typename T, std::
size_t N>
441 requires std::is_arithmetic_v<T>
442struct get_buffer_traits<
Utils::Vector<T, N>> {
444 constexpr static std::size_t dim = N;
447template <
typename Functor>
class ParticleDataSerializer {
448 using RetVal = std::decay_t<std::invoke_result_t<Functor, Particle const &>>;
451 template <
typename T>
452 requires std::is_arithmetic_v<T>
453 void serialize(
auto &buffer, T
const &value)
const {
454 buffer.emplace_back(value);
457 template <
typename T, std::
size_t N>
459 buffer.insert(buffer.end(), value.
cbegin(), value.
cend());
463 explicit ParticleDataSerializer(Functor lambda) : m_getter{lambda} {}
466 auto constexpr value_dim = get_buffer_traits<RetVal>::dim;
467 std::vector<typename get_buffer_traits<RetVal>::type> buffer{};
476template <
typename Functor>
auto make_serializer(Functor lambda) {
477 return ParticleDataSerializer<Functor>{lambda};
479template <
typename RetVal>
480auto make_serializer(RetVal (
Particle::*getter)() const) {
481 auto kernel = [getter](
Particle const &p) -> RetVal {
return (p.*getter)(); };
482 return ParticleDataSerializer<
decltype(kernel)>{std::move(kernel)};
487template <std::
size_t dim,
typename Serializer>
490 HighFive::DataSet &dataset,
491 Serializer serializer) {
492 auto const n_part_local =
static_cast<hsize_t
>(particles.
size());
493 auto const old_extents = dataset.getSpace().getDimensions();
494 auto const extent_n_part =
495 std::max(n_part_global,
static_cast<hsize_t
>(old_extents[1])) -
497 extend_dataset(dataset, detail::slice_info<dim>::extent(extent_n_part));
498 auto const count = detail::slice_info<dim>::count(n_part_local);
499 auto const offset = detail::slice_info<dim>::offset(old_extents[0], prefix);
500 HighFive::DataType dtype = dataset.getDataType();
501 auto buffer = serializer(particles);
502 write_dataset(detail::slice_info<dim>::reshape(buffer, count), dataset,
507 auto const extents = dataset.getSpace().getDimensions();
509 Vector2s const offset{extents[0], 0ul};
512 write_dataset(detail::slice_info<2>::reshape(data, count), dataset, offset,
517 HighFive::DataSet &dataset) {
518 auto const extents = dataset.getSpace().getDimensions();
520 Vector2s const offset{extents[0], 0ul};
522 auto const data = std::vector<double>{lebc.
pos_offset};
523 write_dataset(detail::slice_info<2>::reshape(data, count), dataset, offset,
528 HighFive::DataSet &dataset) {
530 auto const extents = dataset.getSpace().getDimensions();
532 Vector2s const offset{extents[0], 0ul};
534 auto const data = std::vector<int>{shear_direction};
535 write_dataset(detail::slice_info<2>::reshape(data, count), dataset, offset,
540 HighFive::DataSet &dataset) {
542 auto const extents = dataset.getSpace().getDimensions();
544 Vector2s const offset{extents[0], 0ul};
546 auto const data = std::vector<int>{shear_plane_normal};
547 write_dataset(detail::slice_info<2>::reshape(data, count), dataset, offset,
553 auto &datasets = *m_datasets;
555 write_box(box_geo, datasets.at(
"/particles/atoms/box/edges/value"));
560 datasets.at(
"/particles/atoms/lees_edwards/offset/value"));
564 datasets.at(
"/particles/atoms/lees_edwards/direction/value"));
568 datasets.at(
"/particles/atoms/lees_edwards/normal/value"));
572 static_assert(
sizeof(hsize_t) == 8ul);
573 auto const n_part_local =
static_cast<hsize_t
>(particles.
size());
575 BOOST_MPI_CHECK_RESULT(
576 MPI_Exscan, (&n_part_local, &prefix, 1, MPI_UINT64_T, MPI_SUM, m_comm));
577 auto const n_part_global =
578 boost::mpi::all_reduce(m_comm, n_part_local, std::plus<hsize_t>());
580 write_td_particle_property<2>(prefix, n_part_global, particles,
581 datasets.at(
"/particles/atoms/id/value"),
585 HighFive::DataSet &dataset = datasets.at(
"/particles/atoms/id/value");
586 auto const extents = dataset.getSpace().getDimensions();
588 datasets.at(
"/particles/atoms/id/time"),
Vector1s{1},
591 datasets.at(
"/particles/atoms/id/step"),
Vector1s{1},
596 write_td_particle_property<2>(prefix, n_part_global, particles,
597 datasets.at(
"/particles/atoms/species/value"),
601 write_td_particle_property<2>(prefix, n_part_global, particles,
602 datasets.at(
"/particles/atoms/mass/value"),
606 write_td_particle_property<3>(
607 prefix, n_part_global, particles,
608 datasets.at(
"/particles/atoms/position/value"),
609 detail::make_serializer([&](
Particle const &p) {
610 return box_geo.folded_position(p.pos());
614 write_td_particle_property<3>(
615 prefix, n_part_global, particles,
616 datasets.at(
"/particles/atoms/image/value"),
617 detail::make_serializer([&](
Particle const &p) {
618 return box_geo.folded_image_box(p.pos(), p.image_box());
622 write_td_particle_property<3>(
623 prefix, n_part_global, particles,
624 datasets.at(
"/particles/atoms/velocity/value"),
628 write_td_particle_property<3>(prefix, n_part_global, particles,
629 datasets.at(
"/particles/atoms/force/value"),
633 write_td_particle_property<2>(prefix, n_part_global, particles,
634 datasets.at(
"/particles/atoms/charge/value"),
638 write_connectivity(particles);
642void File::write_connectivity(
const ParticleRange &particles) {
644 for (
auto const &p : particles) {
645 auto nbonds_local =
static_cast<decltype(bond)::index
>(bond.shape()[1]);
646 for (
auto const b : p.bonds()) {
647 auto const &partner_ids = b.partner_ids();
648 if (partner_ids.size() == 1u) {
649 bond.resize(boost::extents[1][nbonds_local + 1][2]);
650 bond[0][nbonds_local][0] = p.id();
651 bond[0][nbonds_local][1] = partner_ids[0];
657 auto const n_bonds_local =
static_cast<int>(bond.shape()[1]);
658 auto &datasets = *m_datasets;
659 int prefix_bonds = 0;
660 BOOST_MPI_CHECK_RESULT(
661 MPI_Exscan, (&n_bonds_local, &prefix_bonds, 1, MPI_INT, MPI_SUM, m_comm));
662 auto const n_bonds_total =
663 boost::mpi::all_reduce(m_comm, n_bonds_local, std::plus<int>());
665 datasets.at(
"/connectivity/atoms/value").getSpace().getDimensions();
666 Vector3s offset_bonds = {extents[0],
static_cast<std::size_t
>(prefix_bonds),
668 Vector3s count_bonds = {1,
static_cast<std::size_t
>(n_bonds_local), 2};
669 auto const n_bond_diff = std::max(
static_cast<hsize_t
>(n_bonds_total),
670 static_cast<hsize_t
>(extents[1])) -
672 Vector3s change_extent_bonds = {1,
static_cast<std::size_t
>(n_bond_diff), 0};
673 write_dataset(bond, datasets.at(
"/connectivity/atoms/value"),
674 change_extent_bonds, offset_bonds, count_bonds);
680 auto const view = std::views::elements<0>(
fields_map);
681 return {view.begin(), view.end()};
684File::File(std::filesystem::path file_path, std::filesystem::path script_path,
685 std::vector<std::string>
const &output_fields, std::string mass_unit,
686 std::string length_unit, std::string time_unit,
687 std::string force_unit, std::string velocity_unit,
688 std::string charge_unit,
int chunk_size)
689 : m_file_path(
std::move(file_path)), m_backup_path(m_file_path),
690 m_script_path(
std::move(script_path)),
691 m_absolute_script_path(
692 m_script_path.empty()
693 ?
std::filesystem::path()
694 :
std::filesystem::weakly_canonical(m_script_path)),
695 m_mass_unit(
std::move(mass_unit)), m_length_unit(
std::move(length_unit)),
696 m_time_unit(
std::move(time_unit)), m_force_unit(
std::move(force_unit)),
697 m_velocity_unit(
std::move(velocity_unit)),
698 m_charge_unit(
std::move(charge_unit)), m_chunk_size(chunk_size),
701 m_datasets(
std::make_unique<decltype(m_datasets)::element_type>()),
702 m_h5md_specification(m_fields) {
704 throw std::domain_error(
"Parameter 'chunk_size' must be > 0");
706 m_backup_path +=
".bak";