DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
HDF5Interface.h
1// Copyright (C) 2012-2026 Chris N. Richardson and Garth N. Wells
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include <algorithm>
10#include <array>
11#include <chrono>
12#include <cstddef>
13#include <cstdint>
14#include <dolfinx/common/log.h>
15#include <filesystem>
16#include <format>
17#include <functional>
18#include <hdf5.h>
19#include <mpi.h>
20#include <numeric>
21#include <stdexcept>
22#include <string>
23#include <string_view>
24#include <type_traits>
25#include <utility>
26#include <vector>
27
28namespace dolfinx::io::hdf5
29{
38class Handle
39{
40public:
44 Handle(hid_t id, herr_t (*close)(hid_t)) noexcept : _id(id), _close(close) {}
45
46 // Copy constructor (deleted)
47 Handle(const Handle&) = delete;
48
50 Handle(Handle&& h) noexcept
51 : _id(std::exchange(h._id, H5I_INVALID_HID)), _close(h._close)
52 {
53 }
54
56 ~Handle() { close_or_warn(); }
57
58 // Copy assignment (deleted)
59 Handle& operator=(const Handle&) = delete;
60
62 Handle& operator=(Handle&& h) noexcept
63 {
64 if (this != &h)
65 {
66 close_or_warn();
67 _id = std::exchange(h._id, H5I_INVALID_HID);
68 _close = h._close;
69 }
70 return *this;
71 }
72
74 operator hid_t() const noexcept { return _id; }
75
78 hid_t release() noexcept { return std::exchange(_id, H5I_INVALID_HID); }
79
87 void close()
88 {
89 if (_id > 0)
90 {
91 if (_close(_id) < 0)
92 {
93 throw std::runtime_error(
94 std::format("Failed to close HDF5 identifier {}.", _id));
95 }
96 _id = H5I_INVALID_HID;
97 }
98 }
99
100private:
101 void close_or_warn() noexcept
102 {
103 if (_id > 0 and _close(_id) < 0)
104 logger().warn("Failed to close HDF5 identifier {}.", _id);
105 }
106
107 hid_t _id;
108 herr_t (*_close)(hid_t);
109};
110
113template <typename T>
115 = std::is_same_v<T, float> or std::is_same_v<T, double>
116 or std::is_same_v<T, std::int32_t> or std::is_same_v<T, std::uint32_t>
117 or std::is_same_v<T, std::int64_t> or std::is_same_v<T, std::uint64_t>
118 or std::is_same_v<T, std::uint8_t>;
119
121template <native_scalar T>
122hid_t hdf5_type()
123{
124 if constexpr (std::is_same_v<T, float>)
125 return H5T_NATIVE_FLOAT;
126 else if constexpr (std::is_same_v<T, double>)
127 return H5T_NATIVE_DOUBLE;
128 else if constexpr (std::is_same_v<T, std::int32_t>)
129 return H5T_NATIVE_INT32;
130 else if constexpr (std::is_same_v<T, std::uint32_t>)
131 return H5T_NATIVE_UINT32;
132 else if constexpr (std::is_same_v<T, std::int64_t>)
133 return H5T_NATIVE_INT64;
134 else if constexpr (std::is_same_v<T, std::uint64_t>)
135 return H5T_NATIVE_UINT64;
136 else
137 return H5T_NATIVE_UINT8;
138}
139
145hid_t open_file(MPI_Comm comm, const std::filesystem::path& filename,
146 std::string_view mode, bool use_mpi_io);
147
150void close_file(hid_t handle);
151
154void flush_file(hid_t handle);
155
159std::filesystem::path get_filename(hid_t handle);
160
167bool has_dataset(hid_t handle, std::string_view dataset_path);
168
178void set_attribute(hid_t handle, std::string_view attr_name,
179 std::string_view value);
180
185void set_attribute(hid_t handle, std::string_view attr_name,
186 const std::vector<std::int32_t>& value);
187
192void set_attribute(hid_t handle, std::string_view attr_name,
193 std::int32_t value);
194
200hid_t open_dataset(hid_t handle, std::string_view path);
201
206std::vector<std::int64_t> get_dataset_shape(hid_t handle,
207 std::string_view dataset_path);
208
212void add_group(hid_t handle, std::string_view dataset_path);
213
228template <native_scalar T>
229void write_dataset(hid_t file_handle, std::string_view dataset_path,
230 const T* data, std::array<std::int64_t, 2> range,
231 const std::vector<int64_t>& global_size, bool use_mpi_io,
232 bool use_chunking)
233{
234 // Data rank. Validate before any collective call, so that all ranks
235 // throw together.
236 const int rank = global_size.size();
237 if (rank < 1 or rank > 2)
238 {
239 throw std::invalid_argument(
240 "Cannot write dataset to HDF5 file. Only rank 1 and rank 2 datasets "
241 "are supported.");
242 }
243
244 // Check that group exists and recursively create if required
245 const std::string group_name(dataset_path, 0, dataset_path.rfind('/'));
246 add_group(file_handle, group_name);
247
248 // Null-terminated copy for C API calls
249 const std::string path(dataset_path);
250
251 // Get HDF5 data type
252 const hid_t h5type = hdf5_type<T>();
253
254 // Hyperslab selection parameters
255 std::vector<hsize_t> count(global_size.begin(), global_size.end());
256 count[0] = range[1] - range[0];
257
258 // Data offsets
259 std::vector<hsize_t> offset(rank, 0);
260 offset[0] = range[0];
261
262 // Dataset dimensions
263 const std::vector<hsize_t> dimsf(global_size.begin(), global_size.end());
264
265 Handle dset_id(H5I_INVALID_HID, H5Dclose);
266 if (has_dataset(file_handle, dataset_path))
267 {
268 // Resize existing dataset to the new global size
269 dset_id = Handle(open_dataset(file_handle, dataset_path), H5Dclose);
270 if (H5Dset_extent(dset_id, dimsf.data()) < 0)
271 throw std::runtime_error("Failed to resize HDF5 dataset.");
272 }
273 else
274 {
275 std::vector<hsize_t> maxdims(dimsf.begin(), dimsf.end());
276
277 // Set chunking parameters
278 Handle chunking_properties(
279 use_chunking ? H5Pcreate(H5P_DATASET_CREATE) : H5P_DEFAULT, H5Pclose);
280 if (chunking_properties < 0)
281 throw std::runtime_error("Failed to create HDF5 dataset property list.");
282 if (use_chunking)
283 {
284 // Make array extensible, if chunking is set
285 std::ranges::fill(maxdims, H5S_UNLIMITED);
286
287 // Set chunk size and limit to 1kB min/1MB max. Only the first
288 // `rank` entries are read.
289 std::array<hsize_t, 2> chunk_dims{
290 std::clamp(dimsf[0] / 2, hsize_t(1024), hsize_t(1048576)),
291 rank == 2 ? dimsf[1] : hsize_t(1)};
292 if (H5Pset_chunk(chunking_properties, rank, chunk_dims.data()) < 0)
293 throw std::runtime_error("Failed to set HDF5 chunk size.");
294 }
295
296 // Create a global data space
297 const Handle filespace0(
298 H5Screate_simple(rank, dimsf.data(), maxdims.data()), H5Sclose);
299 if (filespace0 < 0)
300 throw std::runtime_error("Failed to create HDF5 data space");
301
302 // Create global dataset (using dataset_path)
303 dset_id = Handle(H5Dcreate2(file_handle, path.c_str(), h5type, filespace0,
304 H5P_DEFAULT, chunking_properties, H5P_DEFAULT),
305 H5Dclose);
306 if (dset_id < 0)
307 throw std::runtime_error("Failed to create HDF5 global dataset.");
308 }
309
310 const Handle dataspace(H5Dget_space(dset_id), H5Sclose);
311 if (dataspace < 0)
312 throw std::runtime_error("Failed to open HDF5 data space.");
313
314 if (H5Sselect_hyperslab(dataspace, H5S_SELECT_SET, offset.data(), nullptr,
315 count.data(), nullptr)
316 < 0)
317 {
318 throw std::runtime_error("Failed to select HDF5 hyperslab.");
319 }
320
321 // Set parallel access
322 const Handle plist_id(H5Pcreate(H5P_DATASET_XFER), H5Pclose);
323 if (plist_id < 0)
324 throw std::runtime_error("Failed to create HDF5 data transfer list.");
325 if (use_mpi_io)
326 {
327 if (H5Pset_dxpl_mpio(plist_id, H5FD_MPIO_COLLECTIVE) < 0)
328 {
329 throw std::runtime_error(
330 "Failed to set HDF5 data transfer property list.");
331 }
332 }
333
334 // Create a local data space
335 const Handle memspace(H5Screate_simple(rank, count.data(), nullptr),
336 H5Sclose);
337 if (memspace < 0)
338 throw std::runtime_error("Failed to create HDF5 local data space.");
339
340 // Write local dataset
341 if (H5Dwrite(dset_id, h5type, memspace, dataspace, plist_id, data) < 0)
342 throw std::runtime_error("Failed to write HDF5 local dataset.");
343}
344
354template <native_scalar T>
355std::vector<T> read_dataset(hid_t dset_id, std::array<std::int64_t, 2> range,
356 bool allow_cast)
357{
358 auto timer_start = std::chrono::system_clock::now();
359
360 if (!allow_cast)
361 {
362 // Check that HDF5 dataset type and the type T are the same
363 const Handle dtype(H5Dget_type(dset_id), H5Tclose);
364 if (dtype < 0)
365 throw std::runtime_error("Failed to get HDF5 data type.");
366 if (htri_t eq = H5Tequal(dtype, hdf5_type<T>()); eq < 0)
367 throw std::runtime_error("HDF5 datatype equality test failed.");
368 else if (eq == 0)
369 {
370 throw std::runtime_error("Wrong type for reading from HDF5. Use \"h5ls "
371 "-v\" to inspect the types in the HDF5 file.");
372 }
373 }
374
375 // Open dataspace
376 const Handle dataspace(H5Dget_space(dset_id), H5Sclose);
377 if (dataspace < 0)
378 throw std::runtime_error("Failed to open HDF5 data space.");
379
380 // Get rank of data set
381 int rank = H5Sget_simple_extent_ndims(dataspace);
382 if (rank < 1)
383 throw std::runtime_error("Failed to get rank of data space.");
384 else if (rank > 2)
385 logger().warn("io::hdf5::read_dataset untested for rank > 2.");
386
387 // Hyperslab selection, defaulting to the full extent in each
388 // dimension
389 std::vector<hsize_t> offset(rank, 0);
390 std::vector<hsize_t> count(rank);
391 if (H5Sget_simple_extent_dims(dataspace, count.data(), nullptr) != rank)
392 throw std::runtime_error("Failed to get dimensionality of dataspace.");
393 if (range[0] != -1 and range[1] != -1)
394 {
395 offset[0] = range[0];
396 count[0] = range[1] - range[0];
397 }
398
399 // Select a block in the dataset beginning at offset[], with
400 // size=count[]
401 if (H5Sselect_hyperslab(dataspace, H5S_SELECT_SET, offset.data(), nullptr,
402 count.data(), nullptr)
403 < 0)
404 {
405 throw std::runtime_error("Failed to select HDF5 hyperslab.");
406 }
407
408 // Create a memory dataspace
409 const Handle memspace(H5Screate_simple(rank, count.data(), nullptr),
410 H5Sclose);
411 if (memspace < 0)
412 throw std::runtime_error("Failed to create HDF5 dataspace.");
413
414 // Create local data to read into, accumulating in std::size_t so that
415 // the product of the hsize_t extents does not narrow
416 std::vector<T> data(std::reduce(count.begin(), count.end(), std::size_t(1),
417 std::multiplies{}));
418
419 // Read data on each process
420 if (H5Dread(dset_id, hdf5_type<T>(), memspace, dataspace, H5P_DEFAULT,
421 data.data())
422 < 0)
423 {
424 throw std::runtime_error("Failed to read HDF5 data.");
425 }
426
427 auto timer_end = std::chrono::system_clock::now();
428 std::chrono::duration<double> dt = (timer_end - timer_start);
429 double data_rate = data.size() * sizeof(T) / (1e6 * dt.count());
430 logger().info("HDF5 Read data rate: {} MB/s", data_rate);
431
432 return data;
433}
434
448template <native_scalar T>
449std::vector<T> read_dataset(hid_t file_handle, std::string_view dataset_path,
450 std::array<std::int64_t, 2> range, bool allow_cast)
451{
452 const Handle dset_id(open_dataset(file_handle, dataset_path), H5Dclose);
453 return read_dataset<T>(dset_id, range, allow_cast);
454}
455} // namespace dolfinx::io::hdf5
Scope guard for an HDF5 identifier.
Definition HDF5Interface.h:39
Handle(hid_t id, herr_t(*close)(hid_t)) noexcept
Take ownership of an HDF5 identifier.
Definition HDF5Interface.h:44
hid_t release() noexcept
Relinquish ownership of the identifier.
Definition HDF5Interface.h:78
~Handle()
Destructor.
Definition HDF5Interface.h:56
void close()
Close the identifier, reporting a failure.
Definition HDF5Interface.h:87
Handle & operator=(Handle &&h) noexcept
Move assignment.
Definition HDF5Interface.h:62
Handle(Handle &&h) noexcept
Move constructor.
Definition HDF5Interface.h:50
Scalar types that can be read from and written to an HDF5 file.
Definition HDF5Interface.h:115
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24