diff --git a/c++/h5/array_interface.cpp b/c++/h5/array_interface.cpp index 8a9e887e..846468fe 100644 --- a/c++/h5/array_interface.cpp +++ b/c++/h5/array_interface.cpp @@ -15,6 +15,7 @@ // Authors: Olivier Parcollet, Nils Wentzell #include "./array_interface.hpp" +#include "./macros.hpp" #include "./stl/string.hpp" #include @@ -22,58 +23,80 @@ #include #include -#include // DEBUG +#include +#include +#include +#include namespace h5::array_interface { - //------------------------------------------------ - // the dataspace corresponding to the array. Contiguous data only... - dataspace make_mem_dspace(h5_array_view const &v) { + namespace { - if (v.rank() == 0) return H5Screate(H5S_SCALAR); + // Create an HDF5 memory dataspace. + dataspace make_mem_dspace(h5_array_view const &v) { + // scalar case + if (v.rank() == 0) return H5Screate(H5S_SCALAR); - dataspace dspace = H5Screate_simple(v.slab.rank(), v.L_tot.data(), nullptr); - if (!dspace.is_valid()) throw std::runtime_error("Cannot create the dataspace"); + // create a dataspace of rank v.rank() and with shape v.L_tot + dataspace dspace = H5Screate_simple(v.slab.rank(), v.L_tot.data(), nullptr); + if (!dspace.is_valid()) throw std::runtime_error("Error in make_mem_dspace: Creating the dataspace for an h5_array_view failed"); - herr_t err = H5Sselect_hyperslab(dspace, H5S_SELECT_SET, v.slab.offset.data(), v.slab.stride.data(), v.slab.count.data(), - (v.slab.block.empty() ? nullptr : v.slab.block.data())); - if (err < 0) throw std::runtime_error("Cannot set hyperslab"); + // select the hyperslab according to v.slab + herr_t err = H5Sselect_hyperslab(dspace, H5S_SELECT_SET, v.slab.offset.data(), v.slab.stride.data(), v.slab.count.data(), + (v.slab.block.empty() ? nullptr : v.slab.block.data())); + if (err < 0) throw std::runtime_error("Error in make_mem_dspace: Selecting the hyperslab failed"); - return dspace; - } + // return the dataspace + return dspace; + } - //------------------------------------------------ + } // namespace - std::pair get_L_tot_and_strides_h5(long const *stri, int rank, long total_size) { + std::pair get_L_tot_and_strides_h5(long const *np_strides, int rank, long view_size) { + // scalar case: return empty vectors if (rank == 0) return {}; - if (total_size == 0) return {v_t(rank, 0), v_t(rank, 1)}; // if size = 0, we return (0,0,0), (1,1,1) - v_t Ltot(rank), strides_h5(rank); - for (int u = 0; u < rank; ++u) strides_h5[u] = stri[u]; - Ltot[0] = total_size; + // empty nd-array case: return (0,0,0), (1,1,1) + if (view_size == 0) return {v_t(rank, 0), v_t(rank, 1)}; + + // general case + v_t Ltot(rank), h5_strides(rank); + for (int u = 0; u < rank; ++u) h5_strides[u] = np_strides[u]; + Ltot[0] = view_size; for (int u = rank - 2; u >= 0; --u) { - // L[u+1] as gcd of size and stride[u] ... stride[0] - long L = strides_h5[u]; - // L becomes the gcd - for (int v = u - 1; v >= 0; --v) { L = std::gcd(L, strides_h5[v]); } + // L[u+1] as gcd of size and stride[u] ... stride[0] + hsize_t L = h5_strides[u]; + // L becomes the gcd + for (int v = u - 1; v >= 0; --v) { L = std::gcd(L, h5_strides[v]); } // divides - for (int v = u; v >= 0; --v) { strides_h5[v] /= L; } + for (int v = u; v >= 0; --v) { h5_strides[v] /= L; } Ltot[u + 1] = L; } - return {Ltot, strides_h5}; + return {Ltot, h5_strides}; } - //------------------------------------------------------- - // write - //------------------------------------------------------- + h5_lengths_type get_h5_lengths_type(group g, std::string const &name) { + // open dataset + dataset ds = g.open_dataset(name); - void write(group g, std::string const &name, h5_array_view const &v, bool compress) { + // retrieve shape information + datatype ty = H5Dget_type(ds); + bool has_complex_attribute = H5LTfind_attribute(ds, "__complex__"); + dataspace dspace = H5Dget_space(ds); + int rank = H5Sget_simple_extent_ndims(dspace); + v_t dims_out(rank); + H5Sget_simple_extent_dims(dspace, dims_out.data(), nullptr); + return {std::move(dims_out), ty, has_complex_attribute}; + } + + void write(group g, std::string const &name, h5_array_view const &v, bool compress) { + // unlink the dataset if it already exists g.unlink(name); - // Some properties for the dataset : add compression + // chunk the dataset and add compression proplist cparms = H5P_DEFAULT; if (compress and (v.rank() != 0)) { int n_dims = v.rank(); @@ -91,141 +114,130 @@ namespace h5::array_interface { H5Pset_deflate(cparms, 1); } - // dataspace for the dataset in the file + // dataspace for the dataset in the file: v.slab.block shape {1, ..., 1} is assumed dataspace file_dspace = H5Screate_simple(v.slab.rank(), v.slab.count.data(), nullptr); // create the dataset in the file dataset ds = H5Dcreate2(g, name.c_str(), v.ty, file_dspace, H5P_DEFAULT, cparms, H5P_DEFAULT); - if (!ds.is_valid()) throw std::runtime_error("Cannot create the dataset " + name + " in the group " + g.name()); + if (!ds.is_valid()) + throw std::runtime_error("Error in h5::array_interface::write: Creating the dataset " + name + " in the group " + g.name() + " failed"); - // memory data space + // memory dataspace dataspace mem_dspace = make_mem_dspace(v); + + // write to the file dataset if (H5Sget_simple_extent_npoints(mem_dspace) > 0) { // avoid writing empty arrays herr_t err = H5Dwrite(ds, v.ty, mem_dspace, H5S_ALL, H5P_DEFAULT, v.start); - if (err < 0) throw std::runtime_error("Error writing the scalar dataset " + name + " in the group" + g.name()); + if (err < 0) + throw std::runtime_error("Error in h5::array_interface::write: Writing to the dataset " + name + " in the group" + g.name() + " failed"); } - // If we are dealing with complex, we had the attribute + // add complex attribute if the data is complex valued if (v.is_complex) h5_write_attribute(ds, "__complex__", "1"); } - //------------------------------------------------------- - void write_slice(group g, std::string const &name, h5_array_view const &v, h5_lengths_type lt, hyperslab sl) { - + // empty hyperslab if (sl.empty()) return; - if (v.slab.count != sl.count) throw std::runtime_error("Error in h5::write_slice: Slab for memory and file incompatible"); + // check consistency of input: block shape {1, ..., 1} is assumed + if (v.slab.count != sl.count) throw std::runtime_error("Error in h5::array_interface::write_slice: Memory and file slabs are incompatible"); if (not hdf5_type_equal(v.ty, lt.ty)) - throw std::runtime_error("Error in h5::write_slice: Mismatching types. Expecting a " + get_name_of_h5_type(v.ty) - + " while the array stored in the hdf5 file has type " + get_name_of_h5_type(lt.ty)); + throw std::runtime_error("Error in h5::array_interface::write_slice: Incompatible HDF5 types: " + get_name_of_h5_type(v.ty) + + " != " + get_name_of_h5_type(lt.ty)); - // For a sliced write we assume the dataset already exists + // open existing dataset, get dataspace and select hyperslab dataset ds = g.open_dataset(name); dataspace file_dspace = H5Dget_space(ds); + herr_t err = H5Sselect_hyperslab(file_dspace, H5S_SELECT_SET, sl.offset.data(), sl.stride.data(), sl.count.data(), + (sl.block.empty() ? nullptr : sl.block.data())); + if (err < 0) throw std::runtime_error("Error in h5::array_interface::write_slice: Selecting the hyperslab failed"); - herr_t err = H5Sselect_hyperslab(file_dspace, H5S_SELECT_SET, sl.offset.data(), sl.stride.data(), sl.count.data(), - (sl.block.empty() ? nullptr : sl.block.data())); - if (err < 0) throw std::runtime_error("Cannot set hyperslab"); - + // memory dataspace dataspace mem_dspace = make_mem_dspace(v); + + // write to the selected hyperslab of the file dataset if (H5Sget_simple_extent_npoints(file_dspace) > 0) { err = H5Dwrite(ds, v.ty, mem_dspace, file_dspace, H5P_DEFAULT, v.start); - if (err < 0) throw std::runtime_error("Error opening the scalar dataset " + name + " for sliced write in the group " + g.name()); + if (err < 0) + throw std::runtime_error("Error in h5::array_interface::write_slice: Writing the dataset " + name + " in the group " + g.name() + " failed"); } } - //------------------------------------------------------------- - void write_attribute(object obj, std::string const &name, h5_array_view v) { + // check if the attribute already exists + if (H5LTfind_attribute(obj, name.c_str()) != 0) + throw std::runtime_error("Error in h5::array_interface::write_attribute: Attribute " + name + " already exists"); - if (H5LTfind_attribute(obj, name.c_str()) != 0) throw std::runtime_error("The attribute " + name + " is already present. Can not overwrite"); - + // memory dataspace dataspace mem_dspace = make_mem_dspace(v); + // create attribute to write to attribute attr = H5Acreate2(obj, name.c_str(), v.ty, mem_dspace, H5P_DEFAULT, H5P_DEFAULT); - if (!attr.is_valid()) throw std::runtime_error("Cannot create the attribute " + name); + if (!attr.is_valid()) throw std::runtime_error("Error in h5::array_interface::write_attribute: Creating the attribute " + name + " failed"); + // write to the attribute herr_t err = H5Awrite(attr, v.ty, v.start); - if (err < 0) throw std::runtime_error("Cannot write the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5::array_interface::write_attribute: Writing to the attribute " + name + " failed"); } - //------------------------------------------------------- - // READ - //------------------------------------------------------- - - h5_lengths_type get_h5_lengths_type(group g, std::string const &name) { - - dataset ds = g.open_dataset(name); - - bool has_complex_attribute = H5LTfind_attribute(ds, "__complex__"); // the array in file should be interpreted as a complex - dataspace dspace = H5Dget_space(ds); - int rank = H5Sget_simple_extent_ndims(dspace); - - // need to use hsize_t here and the vector is truncated at rank - v_t dims_out(rank); - H5Sget_simple_extent_dims(dspace, dims_out.data(), nullptr); - - // get the type from the file - datatype ty = H5Dget_type(ds); - return {std::move(dims_out), ty, has_complex_attribute}; - } - - //-------------------------------------------------------- - void read(group g, std::string const &name, h5_array_view v, h5_lengths_type lt, hyperslab sl) { - + // open dataset and get dataspace dataset ds = g.open_dataset(name); dataspace file_dspace = H5Dget_space(ds); - // If provided, select the hyperslab of the file data + // if provided, select the hyperslab of the file dataspace if (not sl.empty()) { herr_t err = H5Sselect_hyperslab(file_dspace, H5S_SELECT_SET, sl.offset.data(), sl.stride.data(), sl.count.data(), (sl.block.empty() ? nullptr : sl.block.data())); - if (err < 0) throw std::runtime_error("Cannot set hyperslab"); + if (err < 0) throw std::runtime_error("Error in h5::array_interface::read: selecting the hyperslab failed"); } - // Checks + // check consistency of input if (H5Tget_class(v.ty) != H5Tget_class(lt.ty)) - throw std::runtime_error("Incompatible types in h5_read. Expecting a " + get_name_of_h5_type(v.ty) - + " while the array stored in the hdf5 file has type " + get_name_of_h5_type(lt.ty)); + throw std::runtime_error("Error in h5::array_interface::read: Incompatible HDF5 types: " + get_name_of_h5_type(v.ty) + + " != " + get_name_of_h5_type(lt.ty)); if (not hdf5_type_equal(v.ty, lt.ty)) - std::cerr << "WARNING: Mismatching types in h5_read. Expecting a " + get_name_of_h5_type(v.ty) - + " while the array stored in the hdf5 file has type " + get_name_of_h5_type(lt.ty) + "\n"; + std::cerr << "WARNING: HDF5 type mismatch while reading into an h5_array_view: " + get_name_of_h5_type(v.ty) + + " != " + get_name_of_h5_type(lt.ty) + "\n"; if (lt.rank() != v.rank()) - throw std::runtime_error("h5 read. Rank mismatch : expecting in file a rank " + std::to_string(v.rank()) - + " while the array stored in the hdf5 file has rank " + std::to_string(lt.rank())); + throw std::runtime_error("Error in h5::array_interface::read: Incompatible ranks: " + std::to_string(v.rank()) + + " != " + std::to_string(lt.rank())); - if (sl.empty() and lt.lengths != v.slab.count) throw std::runtime_error("h5 read. Lengths mismatch"); + // block shape of {1, ..., 1} is assumed + if (sl.empty() and lt.lengths != v.slab.count) throw std::runtime_error("Error in h5::array_interface::read: Incompatible shapes"); + // memory dataspace dataspace mem_dspace = make_mem_dspace(v); + + // read the selected hyperslab from the file dataset if (H5Sget_simple_extent_npoints(file_dspace) > 0) { herr_t err = H5Dread(ds, v.ty, mem_dspace, file_dspace, H5P_DEFAULT, v.start); - if (err < 0) throw std::runtime_error("Error reading the scalar dataset " + name + " in the group " + g.name()); + if (err < 0) + throw std::runtime_error("Error in h5::array_interface::read: Reading the dataset " + name + " in the group " + g.name() + " failed"); } } - //------------------------------------------------------------- - void read_attribute(object obj, std::string const &name, h5_array_view v) { - - //if (v.rank() != 0) throw std::runtime_error("Non scalar attribute not implemented"); - + // open attribute attribute attr = H5Aopen(obj, name.c_str(), H5P_DEFAULT); - if (!attr.is_valid()) throw std::runtime_error("Cannot open the attribute " + name); + if (!attr.is_valid()) throw std::runtime_error("Error in h5::array_interface::read_attribute: Opening the attribute " + name + " failed"); + // get dataspace information dataspace space = H5Aget_space(attr); int rank = H5Sget_simple_extent_ndims(space); - if (rank != 0) throw std::runtime_error("Reading a scalar attribute and got rank !=0"); + if (rank != 0) throw std::runtime_error("Error in h5::array_interface::read_attribute: Attribute " + name + " has a rank != 0"); + // get datatype information auto eq = H5Tequal(H5Aget_type(attr), v.ty); - if (eq < 0) throw std::runtime_error("Type comparison failure in reading attribute"); - if (eq == 0) throw std::runtime_error("Type mismatch in reading attribute"); + if (eq < 0) throw std::runtime_error("Error in h5::array_interface::read_attribute: H5Tequal call failed"); + if (eq == 0) throw std::runtime_error("Error in h5::array_interface::read_attribute: Incompatible HDF5 types"); + // read the attribute auto err = H5Aread(attr, v.ty, v.start); - if (err < 0) throw std::runtime_error("Cannot read the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5::array_interface::read_attribute: Reading the attribute " + name + " failed"); } } // namespace h5::array_interface diff --git a/c++/h5/array_interface.hpp b/c++/h5/array_interface.hpp index 247fb5c3..51bf69f1 100644 --- a/c++/h5/array_interface.hpp +++ b/c++/h5/array_interface.hpp @@ -14,99 +14,241 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a generic interface to read/write n-dimensional arrays from/to HDF5. + */ + #ifndef LIBH5_ARRAY_INTERFACE_HPP #define LIBH5_ARRAY_INTERFACE_HPP -#include -#include -#include #include "./group.hpp" +#include "./object.hpp" + +#include +#include -// This is the generic interface for the ndarray interface -// namespace h5::array_interface { - // Stores the hdf5 type and the dims + /// Simple struct to store basic information about an n-dimensional array/dataspace. struct h5_lengths_type { + /// Shape of the array/dataspace. v_t lengths; + + /// h5::datatype stored in the array/dataspace. datatype ty; + + /// Whether the stored values are complex. bool has_complex_attribute; - // - [[nodiscard]] int rank() const { return lengths.size(); } + /// Get the rank of the array/dataspace. + [[nodiscard]] int rank() const { return static_cast(lengths.size()); } }; - // Store HDF5 hyperslab info, as in HDF5 manual - // http://davis.lbl.gov/Manuals/HDF5-1.8.7/UG/12_Dataspaces.html + /** + * @brief Struct representing an HDF5 hyperslab. + * + * @details A hyperslab is used to select elements form an n-dimensional array/dataspace and it is defined + * by 4 arrays of the same size as the rank of the underlying dataspace + * (see HDF5 docs): + * - `offset`: Origin of the hyperslab in the dataspace. + * - `stride`: The number of elements to skip between each element or block to be selected. If the stride + * parameter is set to `NULL`, the stride size defaults to 1 in each dimension. + * - `count`: The number of elements or blocks to select along each dimension. + * - `block`: The size of a block selected from the dataspace. If the block parameter is set to `NULL`, + * the block size defaults to a single element in each dimension, as if the block array was set to all ones. + * + * The imaginary part of a complex array/dataspace is treated as just another dimension, i.e. its rank is + * increased by one. + * + * The following example selects every second column in a `7x7` dataspace: + * + * @code + * h5::array_interface::hyperslab slab; + * slab.offset = {0, 0}; + * slab.stride = {1, 2}; + * slab.count = {7, 4}; + * slab.block = {1, 1}; + * @endcode + * + * For a complex valued dataspace, the same example would look like this: + * + * @code + * h5::array_interface::hyperslab slab; + * slab.offset = {0, 0, 0}; + * slab.stride = {1, 2, 1}; + * slab.count = {7, 4, 2}; + * slab.block = {1, 1, 1}; + * @endcode + */ struct hyperslab { - v_t offset; // index offset for each dimension - v_t stride; // stride in each dimension (in the HDF5 sense). 1 if contiguous. Always >0. - v_t count; // length in each dimension - v_t block; // block in each dimension + /// Index offset for each dimension. + v_t offset; + + /// Stride in each dimension (in the HDF5 sense). + v_t stride; + + /// Number of elements or blocks to select along each dimension. + v_t count; - // Constructor : unique to enforce the proper sizes of array - // rank : the array will have rank + is_complex + /// Shape of a single block selected from the dataspace. + v_t block; + + /** + * @brief Construct a new empty hyperslab for a dataspace of a given rank. + * + * @details A complex hyperslab has an additional dimension for the imaginary part. By default, `offset` and + * `count` are set to zero and `stride` and `block` are set to one. If complex valued, `count.back() = 2`. + * + * @param rank Rank of the underlying dataspace (excluding the possible added imaginary dimension). + * @param is_complex Whether the data is complex valued. + */ hyperslab(int rank, bool is_complex) - : offset(rank + is_complex, 0), - stride(rank + is_complex, 1), - count(rank + is_complex, 0), - block(rank + is_complex, 1) { // block is often unused + : offset(rank + is_complex, 0), stride(rank + is_complex, 1), count(rank + is_complex, 0), block(rank + is_complex, 1) { if (is_complex) { stride[rank] = 1; count[rank] = 2; } } + /// Default constructor leaves the hyperslab empty (uninitialized). hyperslab() = default; - // - [[nodiscard]] int rank() const { return count.size(); } + /// Get the rank of the hyperslab (including the possible added imaginary dimension). + [[nodiscard]] int rank() const { return static_cast(count.size()); } + + /// Check whether the hyperslab is empty (has been initialized). [[nodiscard]] bool empty() const { return count.empty(); } }; - // Stores a view of an array. - // scalar are array of rank 0, lengths, strides are empty, rank is 0, start is the scalar + /** + * @brief Struct representing a view on an n-dimensional array/dataspace. + * + * @details A view consists of the parent array and of an h5::array_interface::hyperslab + * specifying a selection. The array is defined by a pointer to its data and its shape. If + * the data of the array is complex, its imaginary part is treated as just another dimension. + */ struct h5_array_view { - datatype ty; // HDF5 type - void *start; // start of data. It MUST be a pointer of T* with ty = hdf5_type() - v_t L_tot; // lengths of the parent contiguous array - hyperslab slab; // hyperslab + /// h5::datatype stored in the array. + datatype ty; + + /// Pointer to the data of the array. + void *start; + + /// Shape of the (contiguous) parent array. + v_t L_tot; + + /// h5::array_interface::hyperslab specifying the selection of the view. + hyperslab slab; + + /// Whether the data is complex valued. bool is_complex; - // Constructor : unique to enforce the proper sizes of array + /** + * @brief Construct a new empty array view. + * + * @details A complex view has an additional dimension for the imaginary part. The shape of the + * parent array is left uninitialized and the h5::array_interface::hyperslab is empty. + * + * @param ty h5::datatype of the array. + * @param start Pointer to the data of the parent array. + * @param rank Rank of the parent array (excluding the possible added imaginary dimension). + * @param is_complex Whether the data is complex valued. + */ h5_array_view(datatype ty, void *start, int rank, bool is_complex) : ty(std::move(ty)), start(start), L_tot(rank + is_complex), slab(rank, is_complex), is_complex(is_complex) { if (is_complex) L_tot[rank] = 2; } + /// Get the rank of the view (including the possible added imaginary dimension). [[nodiscard]] int rank() const { return slab.rank(); } }; - //------------------------------------------------ - // Given the array strides, rank and size return the total dimensions L_tot and the h5-strides - // Assume stride_order is C. - // use strides[rank -1] = strides_h5 [rank -1] - // strides[rank -2] = L[rank-1] * strides_h5[rank -2] - // strides[rank -3] = L[rank-1] * L[rank-2] * strides_h5[rank -3] - // strides[0] = L[rank-1] * L[rank-2] * L[1] * strides_h5[0] - std::pair get_L_tot_and_strides_h5(long const *stri, int rank, long total_size); + /** + * @brief Given a view on an n-dimensional array (dataspace) by specifying its numpy/nda-style strides and + * its size, calculate the shape of the underlying parent array and the HDF5 strides of the view. + * + * @warning This function contains a bug and only works as intended in special cases. + * + * @details The memory layout is assumend to be in C-order. Suppose `L` is an array containing the shape of the + * n-dimensional parent array, `np_strides` contains the numpy strides and `h5_strides` are the HDF5 strides. Then + * - `np_strides[n - 1] = h5_strides[n - 1]`, + * - `np_strides[n - i] = L[n - 1] * ... * L[n - i - 1] * h5_strides[n - i]` and + * - `np_strides[0] = L[n - 1] * ... * L[1] * h5_strides[0]`. + * + * @param np_strides Numpy/nda-style strides. + * @param rank Rank of the n-dimensional parent array. + * @param view_size Number of elements in the given view. + * @return std::pair containing the shape of the parent array and the HDF5 strides of the view. + */ + std::pair get_L_tot_and_strides_h5(long const *np_strides, int rank, long view_size); - // Retrieve lengths and hdf5 type from a dataset g[name] or attribute obj[name] + /** + * @brief Retrieve the shape and the h5::datatype from a dataset. + * + * @param g h5::group containing the dataset. + * @param name Name of the dataset. + * @return h5::array_interface::h5_lengths_type containing the shape and HDF5 type of the dataset. + */ h5_lengths_type get_h5_lengths_type(group g, std::string const &name); - // Write the view of the array to the group - void write(group g, std::string const &name, h5_array_view const &a, bool compress); - - // Write to part of an existing dataset in file - void write_slice(group g, std::string const &name, h5_array_view const &a, h5_lengths_type lt, hyperslab sl); + /** + * @brief Write an array view to an HDF5 dataset. + * + * @details If a link with the given name already exists, it is first unlinked. + * + * @warning This function only works consistently if the blocks of the hyperslab contain only a single element! + * + * @param g h5::group in which the dataset is created. + * @param name Name of the dataset + * @param v h5::array_interface::h5_array_view to be written. + * @param compress Whether to compress the dataset. + */ + void write(group g, std::string const &name, h5_array_view const &v, bool compress); - // Read into an array_view from the group - void read(group g, std::string const &name, h5_array_view v, h5_lengths_type lt, hyperslab sl = {}); + /** + * @brief Write an array view to a selected hyperslab of an existing HDF5 dataset. + * + * @details It checks if the number of elements in the view is the same as selected in the hyperslab and if the + * datatypes are compatible. Otherwise, an exception is thrown. + * + * @warning This function only works consistently if the blocks of both hyperslabs contain only a single element! + * + * @param g h5::group which contains the dataset. + * @param name Name of the dataset. + * @param v h5::array_interface::h5_array_view to be written. + * @param lt h5::array_interface::h5_lengths_type of the file dataset (only used to check the consistency of the input). + * @param sl h5::array_interface::hyperslab specifying the selection to be written to. + */ + void write_slice(group g, std::string const &name, h5_array_view const &v, h5_lengths_type lt, hyperslab sl); - // Write the view of the array to the attribute + /** + * @brief Write an array view to an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v v h5::array_interface::h5_array_view to be written. + */ void write_attribute(object obj, std::string const &name, h5_array_view v); - // Read into a contiguous array_view from the attribute + /** + * @brief Read a given hyperslab from an HDF5 dataset into an array view. + * + * @param g h5::group which contains the dataset. + * @param name Name of the dataset. + * @param v h5::array_interface::h5_array_view to read into. + * @param lt h5::array_interface::h5_lengths_type of the file dataset (only used to check the consistency of the input). + * @param sl h5::array_interface::hyperslab specifying the selection to read from. + */ + void read(group g, std::string const &name, h5_array_view v, h5_lengths_type lt, hyperslab sl = {}); + + /** + * @brief Read from an HDF5 attribute into an array view. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v h5::array_interface::h5_array_view to read into. + */ void read_attribute(object obj, std::string const &name, h5_array_view v); } // namespace h5::array_interface diff --git a/c++/h5/file.cpp b/c++/h5/file.cpp index c14555b1..38bb21f8 100644 --- a/c++/h5/file.cpp +++ b/c++/h5/file.cpp @@ -15,110 +15,120 @@ // Authors: Olivier Parcollet, Nils Wentzell #include "./file.hpp" + #include #include -#include + +#include using namespace std::string_literals; +// throw an exception with a given message if a condition is not met #define CHECK_OR_THROW(Cond, Mess) \ - if (!(Cond)) throw std::runtime_error("Error in h5 interface : "s + Mess); + if (!(Cond)) throw std::runtime_error("Error in h5::file: "s + (Mess)); namespace h5 { file::file(const char *name, char mode) { switch (mode) { + // open existing file in read only mode case 'r': id = H5Fopen(name, H5F_ACC_RDONLY, H5P_DEFAULT); break; + // create new or overwrite existing file in read-write mode case 'w': id = H5Fcreate(name, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); break; - case 'a': - // Turn off error handling - herr_t (*old_func)(void *); - void *old_client_data; + // create new or append to exisiting file in read-write mode + case 'a': { + // turn off error handling + herr_t (*old_func)(void *) = nullptr; + void *old_client_data = nullptr; H5Eget_auto1(&old_func, &old_client_data); H5Eset_auto1(nullptr, nullptr); - // This may fail + // this may fail id = H5Fcreate(name, H5F_ACC_EXCL, H5P_DEFAULT, H5P_DEFAULT); - // Turn on error handling + // turn on error handling H5Eset_auto1(old_func, old_client_data); - // Open in RDWR if creation failed + // open in read-write mode if creation failed if (id < 0) id = H5Fopen(name, H5F_ACC_RDWR, H5P_DEFAULT); break; + } + // create new file in read-write mode if the file does not exist yet case 'e': id = H5Fopen(name, H5F_ACC_EXCL, H5P_DEFAULT); break; - default: throw std::runtime_error("HDF5 file opening : mode is not r, w, a, e. Cf documentation"); + default: throw std::runtime_error("File mode is not one of r, w, a, e"); } - if (id < 0) throw std::runtime_error("HDF5 : cannot "s + (((mode == 'r') or (mode == 'a')) ? "open" : "create") + "file : "s + name); + // throw an exception if opening/creating the file failed + if (id < 0) throw std::runtime_error("Opening/Creating the file "s + name + " failed"); } - //--------------------------------------------- + // same function is used in h5::group + std::string file::name() const { + // get length of the name + ssize_t size = H5Fget_name(id, nullptr, 1); - std::string file::name() const { // same function as for group - char _n[1]; - ssize_t size = H5Fget_name(id, _n, 1); // first call, get the size only + // reserve a buffer and get name std::vector buf(size + 1, 0x00); - H5Fget_name(id, buf.data(), size + 1); // now get the name + H5Fget_name(id, buf.data(), size + 1); + + // return string std::string res = ""; res.append(&(buf.front())); return res; } - //--------------------------------------------- - void file::flush() { if (not is_valid()) return; auto err = H5Fflush(id, H5F_SCOPE_GLOBAL); - CHECK_OR_THROW((err >= 0), "flushing the file"); + CHECK_OR_THROW((err >= 0), "Flushing the file failed"); } - // ------------------------- - file::file() { - + // create a file access property list proplist fapl = H5Pcreate(H5P_FILE_ACCESS); - CHECK_OR_THROW((fapl >= 0), "creating fapl"); + CHECK_OR_THROW((fapl >= 0), "Creating the fapl failed"); + // set the file driver to use the `H5FD_CORE` driver auto err = H5Pset_fapl_core(fapl, (size_t)(64 * 1024), false); - CHECK_OR_THROW((err >= 0), "setting core file driver in fapl."); + CHECK_OR_THROW((err >= 0), "Setting the core file driver in fapl failed"); + // create a buffered memory file this->id = H5Fcreate("MemoryBuffer", 0, H5P_DEFAULT, fapl); - CHECK_OR_THROW((this->is_valid()), "created core file"); + CHECK_OR_THROW((this->is_valid()), "Creating a buffered memory file failed"); } - // ------------------------- - file::file(const std::byte *buf, size_t size) { - + // create a file access property list proplist fapl = H5Pcreate(H5P_FILE_ACCESS); - CHECK_OR_THROW((fapl >= 0), "creating fapl"); + CHECK_OR_THROW((fapl >= 0), "Creating the fapl failed"); + // set the file driver to use the `H5FD_CORE` driver auto err = H5Pset_fapl_core(fapl, (size_t)(64 * 1024), false); - CHECK_OR_THROW((err >= 0), "setting core file driver in fapl."); + CHECK_OR_THROW((err >= 0), "Setting the core file driver in fapl failed"); + // set the initial file image to the given memory buffer err = H5Pset_file_image(fapl, (void *)buf, size); - CHECK_OR_THROW((err >= 0), "set file image in fapl."); + CHECK_OR_THROW((err >= 0), "Setting the file image to a given memory buffer failed"); + // create a buffered memory file this->id = H5Fopen("MemoryBuffer", H5F_ACC_RDWR, fapl); - CHECK_OR_THROW((this->is_valid()), "opened received file image file"); + CHECK_OR_THROW((this->is_valid()), "Creating a buffered memory file failed"); } - // ------------------------- - std::vector file::as_buffer() const { - + // flush the file auto f = hid_t(*this); auto err = H5Fflush(f, H5F_SCOPE_GLOBAL); - CHECK_OR_THROW((err >= 0), "flushed core file."); + CHECK_OR_THROW((err >= 0), "Flushing the buffered memory file failed"); + // retrieve size of the file image ssize_t image_len = H5Fget_file_image(f, nullptr, (size_t)0); - CHECK_OR_THROW((image_len > 0), "got image file size"); + CHECK_OR_THROW((image_len > 0), "Getting the file image size failed"); + // create buffer and copy file image to it std::vector buf(image_len, std::byte{0}); - ssize_t bytes_read = H5Fget_file_image(f, (void *)buf.data(), (size_t)image_len); - CHECK_OR_THROW(bytes_read == image_len, "wrote file into image buffer"); + CHECK_OR_THROW(bytes_read == image_len, "Writing file image to buffer failed"); return buf; } diff --git a/c++/h5/file.hpp b/c++/h5/file.hpp index 8399b720..38c40d6d 100644 --- a/c++/h5/file.hpp +++ b/c++/h5/file.hpp @@ -14,62 +14,87 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a handle to an HDF5 file. + */ + #ifndef LIBH5_FILE_HPP #define LIBH5_FILE_HPP -#include -#include #include "./object.hpp" +#include +#include +#include +#include + namespace h5 { /** - * A little handler for the HDF5 file + * @brief A handle to an HDF5 file. * - * The class is basically a pointer to the file. + * @details This class inherits from the general h5::object class. It simply wraps the HDF5 functions + * to open an existing file or to create a new file. + * + * An h5::file is automatically closed when it goes out of scope, i.e. its reference count is decreased. */ class file : public object { - public: /** - * Open a file in memory + * @brief Default constructor creates a buffered memory file. + * @details It modifies the file access property list to use the `H5FD_CORE` driver. It can be used for + * serializing and deserializing data (see h5::serialize and h5::deserialize). */ file(); /** - * Open the file on disk + * @brief Constructor to open an existing file or to create a new file on disk. * - * @param name name of the file + * @details The file is opened in the specified mode. The following modes are available: * - * @param mode Opening mode - * - * - 'r' : Read Only (HDF5 flag H5F_ACC_RDONLY) - * - 'w' : Write Only (HDF5 flag H5F_ACC_TRUNC) - * - 'a' : Append (HDF5 flag H5F_ACC_RDWR) - * - 'e' : Like 'w' but fails if the file already exists (HDF5 flag H5F_ACC_EXCL) + * - 'r': Open an existing file in read only mode (calls `H5Fopen` with `H5F_ACC_RDONLY`). + * - 'w': Create a new file or overwrite an existing file in read-write mode (calls `H5Fcreate` with `H5F_ACC_TRUNC`). + * - 'a': Create a new file or append to an existing file in read-write mode (calls `H5Fcreate` with `H5F_ACC_EXCL` + * or `H5Fopen` with `H5F_ACC_RDWR` in case the file already exists). + * - 'e': Create a new file if the file does not already exists, otherwise throw an exception (calls `H5Fcreate` with + * `H5F_ACC_EXCL`) + * + * @param name Name of the file. + * @param mode Mode in which to open the file. */ file(const char *name, char mode); - // Open the file on disk + /** + * @brief Constructor to open an existing file or to create a new file on disk. + * @details See file::file(const char*, char) for a more detailed description. + */ file(std::string const &name, char mode) : file(name.c_str(), mode) {} - /// Name of the file + /// Get the name of the file. [[nodiscard]] std::string name() const; - /// Flush the file + /// Flush the file by calling `H5Fflush`. void flush(); private: + // Constructor to create a buffered memory file with an initial file image of a given size. file(const std::byte *buf, size_t size); public: - /// Create a file in memory from a byte buffer + /** + * @brief Constructor to create a buffered memory file from a byte buffer. + * @param buf Byte buffer. + */ file(std::span const &buf) : file(buf.data(), buf.size()) {} - /// Create a file in memory from a byte buffer + /** + * @brief Constructor to create a buffered memory file from a byte buffer. + * @param buf Byte buffer. + */ file(std::vector const &buf) : file(buf.data(), buf.size()) {} - /// Get a copy of the associated byte buffer + /// Get a copy of the associated byte buffer. [[nodiscard]] std::vector as_buffer() const; }; diff --git a/c++/h5/format.cpp b/c++/h5/format.cpp index 22f4e299..6e47f8a4 100644 --- a/c++/h5/format.cpp +++ b/c++/h5/format.cpp @@ -15,36 +15,40 @@ // Authors: Olivier Parcollet, Nils Wentzell #include "./format.hpp" +#include "./group.hpp" +#include "./object.hpp" #include "./stl/string.hpp" + +#include #include namespace h5 { void read_hdf5_format(object obj, std::string &s) { h5_read_attribute(obj, "Format", s); - if (s == "") { // Backward compatibility - h5_read_attribute(obj, "TRIQS_HDF5_data_scheme", s); - } + + // backward compatibility + if (s == "") { h5_read_attribute(obj, "TRIQS_HDF5_data_scheme", s); } } std::string read_hdf5_format(group g) { std::string s; - read_hdf5_format(g, s); + read_hdf5_format(g, s); // NOLINT (slicing is intended) return s; } void read_hdf5_format_from_key(group g, std::string const &key, std::string &s) { h5_read_attribute_from_key(g, key, "Format", s); - if (s == "") { // Backward compatibility - h5_read_attribute_from_key(g, key, "TRIQS_HDF5_data_scheme", s); - } + + // backward compatibility + if (s == "") { h5_read_attribute_from_key(g, key, "TRIQS_HDF5_data_scheme", s); } } void assert_hdf5_format_as_string(group g, const char *tag_expected, bool ignore_if_absent) { - auto tag_file = read_hdf5_format(g); - if (ignore_if_absent and tag_file.empty()) return; - if (tag_file != tag_expected) - throw std::runtime_error("h5_read : mismatch of the Format tag in the h5 group : found " + tag_file + " while I expected " + tag_expected); + auto tag = read_hdf5_format(g); + if (ignore_if_absent and tag.empty()) return; + if (tag != tag_expected) + throw std::runtime_error("Error in assert_hdf5_format_as_string: hdf5_format tag mistmatch: " + tag + " != " + tag_expected); } } // namespace h5 diff --git a/c++/h5/format.hpp b/c++/h5/format.hpp index 41182ebd..ccebcbe8 100644 --- a/c++/h5/format.hpp +++ b/c++/h5/format.hpp @@ -14,33 +14,40 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides utilities for reading and writing `hdf5_format` tags. + * @details An `hdf5_format` tag is a string that describes the type of object stored in an HDF5 file. A type `T` which is + * HDF5 readable/writeable should do one of the following: + * - implement a static member function `static hdf5_format() -> std::string` or + * - specialize the `hdf5_format_impl` struct for `T` and provide an implementation for the member `static invoke() -> std::string`. + */ + #ifndef LIBH5_FORMAT_HPP #define LIBH5_FORMAT_HPP #include "./macros.hpp" #include "./group.hpp" #include "./stl/string.hpp" + #include #include namespace h5 { - // a class T has either : - // 1- a static member hdf5_format -> std::string (or a constexpr char * ?) - // 2- specializes hdf5_format_impl - // user function is get_hdf5_format () in all cases. - // A claass which is not default constructible : - // -- 1 : implement static T h5_read_construct(gr, name) : rebuilt a new T - // -- 2 : NOT IMPLEMENTED : if we want to make it non intrusive, - // specialize with a struct similarly to hdf5_format_impl - // to be implemented if needed. - + /** + * @brief Default type trait to get the `hdf5_format` tag of type `T` by calling its static member function + * `T::hdf5_format()`. + * + * @tparam T Type for which the `hdf5_format` tag is to be retrieved. + */ template struct hdf5_format_impl { static std::string invoke() { return T::hdf5_format(); } }; #define H5_SPECIALIZE_FORMAT2(X, Y) \ + /** @brief Specialization of h5::hdf5_format_impl for X. */ \ template <> \ struct hdf5_format_impl { \ static std::string invoke() { return H5_AS_STRING(Y); } \ @@ -60,35 +67,94 @@ namespace h5 { H5_SPECIALIZE_FORMAT(long double); H5_SPECIALIZE_FORMAT2(std::complex, complex); + /** + * @brief Get the `hdf5_format` tag of type `T`. + * + * @tparam T Type for which the `hdf5_format` tag is to be retrieved.. + * @return std::string containing the `hdf5_format` tag. + */ template std::string get_hdf5_format() { return hdf5_format_impl::invoke(); } + /** + * @brief Get the `hdf5_format` tag of type `T` using template argument deduction. + * + * @tparam T Type for which the `hdf5_format` tag is to be retrieved.. + * @return std::string containing the `hdf5_format` tag. + */ template std::string get_hdf5_format(T const &) { return hdf5_format_impl::invoke(); } + /** + * @brief Write a std::string to an HDF5 attribute with the name 'Format'. + * + * @param obj h5::object to which the attribute is attached. + * @param s String to be written. + */ inline void write_hdf5_format_as_string(object obj, std::string const &s) { h5_write_attribute(obj, "Format", s); } - // Write the h5 format tag to the object + /** + * @brief Write an `hdf5_format` tag for type `T` to an HDF5 attribute with the name 'Format' using template + * argument deduction. + * + * @tparam T Type for which the `hdf5_format` tag is to be written. + * @param obj h5::object to which the attribute is attached. + */ template inline void write_hdf5_format(object obj, T const &) { h5_write_attribute(obj, "Format", get_hdf5_format()); } - /// Read h5 format tag from the object + /** + * @brief Read an `hdf5_format` tag from an HDF5 attribute with the name 'Format'. + * + * @param obj h5::object from which the attribute is read. + * @param s String to be read into. + */ void read_hdf5_format(object obj, std::string &s); + + /** + * @brief Read an `hdf5_format` tag from an HDF5 attribute with the name 'Format' attached to a given h5::group. + * + * @param g h5::group from which the attribute is read. + * @return String containing the `hdf5_format` tag. + */ std::string read_hdf5_format(group g); - /// Add the h5 format tag to the key in the group + /** + * @brief Read an `hdf5_format` tag from an HDF5 attribute with the name 'Format'. + * + * @param g h5::group containing the HDF5 object from which the attribute is read. + * @param key Name of the object. + * @param s String to be read into. + */ void read_hdf5_format_from_key(group g, std::string const &key, std::string &s); - /// Asserts that the tag of the group is the same as the given string. Throws std::runtime_error if incompatible + /** + * @brief Assert that the `hdf5_format` tag attached to the given group is the same as the given tag. + * + * @details Throws a std::runtime_error if the tags don't match. + * + * @param g h5::group to be checked. + * @param tag_expected Expected `hdf5_format` tag. + * @param ignore_if_absent If true, the assertion is ignored if the group does not have a 'Format' attribute. + */ void assert_hdf5_format_as_string(group g, const char *tag_expected, bool ignore_if_absent = false); - /// Asserts that the tag of the group is the same as for T. Throws std::runtime_error if incompatible + /** + * @brief Assert that the `hdf5_format` tag attached to the given group is the same as the `hdf5_format` tag of the type `T` + * using template argument deduction. + * + * @details Throws a std::runtime_error if the tags don't match. + * + * @tparam T Type for which the `hdf5_format` tag is to be checked. + * @param g h5::group to be checked. + * @param ignore_if_absent If true, the assertion is ignored if the group does not have a 'Format' attribute. + */ template void assert_hdf5_format(group g, T const &, bool ignore_if_absent = false) { assert_hdf5_format_as_string(g, get_hdf5_format().c_str(), ignore_if_absent); diff --git a/c++/h5/generic.hpp b/c++/h5/generic.hpp index 5b06f388..14059ec4 100644 --- a/c++/h5/generic.hpp +++ b/c++/h5/generic.hpp @@ -14,54 +14,102 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a generic interface for reading/writing data from/to various HDF5 objects. + * @details The generic functions (h5::read, h5::write, h5::read_attribute, ...) call their more specialized counterparts + * (h5::h5_read, h5::h5_write, h5::h5_read_attribute, ...) which find the correct implementation using ADL. + */ + #ifndef LIBH5_GENERIC_HPP #define LIBH5_GENERIC_HPP +#include "./group.hpp" + +#include #include namespace h5 { /** - * A generic read of a key in a group + * @brief Generic implementation for reading from an HDF5 dataset/subgroup. * - * @tparam T The C++ type to be read - * @param g The h5::group - * @param key Name of the element - * @return The value read from the file + * @details It calls the static member function `T::h5_read_construct(group, T &)` in case `T` is not default constructible, + * otherwise it calls the specialized `h5_read(group, std::string const &, T &)` with a default constructed `T` object. + * + * @tparam T C++ type to be read. + * @param g h5::group containing the dataset/subgroup from which to read from. + * @param key Name of the dataset/subgroup. + * @return Data read from the dataset/subgroup. */ template T h5_read(group g, std::string const &key) { + // default constructible if constexpr (std::is_default_constructible_v) { T x{}; h5_read(g, key, x); return x; - } else { + } else { // not default constructible return T::h5_read_construct(g, key); } } + /** + * @brief Generic implementation for reading from an HDF5 dataset/subgroup. + * + * @details It simply calls h5::h5_read(group, std::string const &). + * + * @tparam T C++ type to be read. + * @param g h5::group containing the dataset/subgroup from which to read from. + * @param key Name of the dataset/subgroup. + * @return Data read from the dataset/subgroup. + */ template T read(group g, std::string const &key) { return h5_read(g, key); } + /** + * @brief Generic implementation for reading from an HDF5 dataset/subgroup into a given variable. + * + * @details It calls the specialized `h5_read(group, std::string const &, T &, auto const &...)` for the given `T`. + * + * @tparam T C++ type to be read. + * @param g h5::group containing the dataset/subgroup from which to read from. + * @param key Name of the dataset/subgroup. + * @param x Variable to read into. + * @param args Additional arguments to be passed to the specialized `h5_read(group, std::string const &, T &)` function. + */ template void read(group g, std::string const &key, T &x, auto const &...args) { h5_read(g, key, x, args...); } + /** + * @brief Generic implementation for writing a variable to an HDF5 dataset/subgroup. + * + * @details It calls the specialized `h5_write(group, std::string const &, T const &)` for the given `T`. + * + * @tparam T C++ type to be written. + * @param g h5::group in which the dataset/subgroup is created. + * @param key Name of the dataset/subgroup to which the variable is written. + * @param x Variable to be written. + * @param args Additional arguments to be passed to the specialized `h5_write(group, std::string const &, T const &)` function. + */ template void write(group g, std::string const &key, T const &x, auto const &...args) { h5_write(g, key, x, args...); } /** - * A generic attribute read from an object + * @brief Generic implementation for reading an HDF5 attribute. * - * @tparam T The C++ type of the attribute - * @param obj The object to read the attribute from - * @param name Name of the attribute - * @return The attribute object, and "" if the attribute does not exist. + * @details `T` needs to be default constructible. + * + * @tparam T C++ type to be read. + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @return Data read from the attribute. */ template T h5_read_attribute(object obj, std::string const &name) { @@ -70,29 +118,62 @@ namespace h5 { return x; } + /** + * @brief Generic implementation for reading an HDF5 attribute. + * + * @details It simply calls h5::h5_read_attribute(object, std::string const &). + * + * @tparam T C++ type to be read. + * @param obj h5::object to which the attribute is attached. + * @param key Name of the attribute. + * @return Data read from the attribute. + */ template T read_attribute(object obj, std::string const &key) { return h5_read_attribute(obj, key); } + /** + * @brief Generic implementation for reading an HDF5 attribute into a given variable. + * + * @details It calls the specialized `h5_read_attribute(object, std::string const &, T &)` for the given `T`. + * + * @tparam T C++ type to be read. + * @param obj h5::object to which the attribute is attached. + * @param key Name of the attribute. + * @param x Variable to read into. + */ template void read_attribute(object obj, std::string const &key, T &x) { h5_read_attribute(obj, key, x); } + /** + * @brief Generic implementation for writing a variable to an HDF5 attribute. + * + * @details It calls the specialized `h5_write_attribute(object, std::string const &, T const &)` for the given `T`. + * + * @tparam T C++ type to be read. + * @param obj h5::object to which the attribute is attached. + * @param key Name of the attribute to be created. + * @param x Variable to be written. + */ template void write_attribute(object obj, std::string const &key, T const &x) { h5_write_attribute(obj, key, x); } /** - * A generic attribute read from a key in an h5::group + * @brief Generic implementation for reading an HDF5 attribute. + * + * @details It calls the specialized `h5_read_attribute_from_key(object, std::string const &, std::string const &, T &)` + * for the given `T`. `T` needs to be default constructible. * - * @tparam T The C++ type of the attribute - * @param g The h5::group - * @param key The key - * @param name Name of the attribute - * @return The attribute object, and "" if the attribute does not exist. + * @tparam T C++ type to be read. + * @param g h5::group containing the HDF5 object to which the attribute is attached. + * @param key Name of the object. + * @param name Name of the attribute. + * @return Data read from the attribute. */ template T h5_read_attribute_from_key(group g, std::string const &key, std::string const &name) { @@ -102,16 +183,19 @@ namespace h5 { } /** - * Try a generic read of a key in a group. + * @brief Check if an HDF5 dataset/subgroup with the given key exists in the given parent group before performing the read. + * + * @details If the key exists, it simply calls the specialized `h5_read(group, std::string const &, T &)` for the given `T`, + * otherwise it does nothing. * - * @tparam T The C++ type to be read - * @param g HDF5 group - * @param key Name of the element - * @param x Parameter to read. - * @return true if the read succeeds, false if it fails + * @tparam T C++ type to be read. + * @param g h5::group containing the dataset/subgroup from which to read from. + * @param key Name of the dataset/subgroup. + * @param x Variable to read into. + * @return True, if the dataset/subgroup exists and reading was successful, false otherwise. */ template - inline bool try_read(group g, std::string key, T &x) { + inline bool try_read(group g, std::string const &key, T &x) { if (g.has_key(key)) { h5_read(g, key, x); return true; diff --git a/c++/h5/group.cpp b/c++/h5/group.cpp index e27fbb49..3ccc5ba2 100644 --- a/c++/h5/group.cpp +++ b/c++/h5/group.cpp @@ -14,38 +14,32 @@ // // Authors: Henri Menke, Olivier Parcollet, Nils Wentzell -#include #include "./group.hpp" -#include "./stl/string.hpp" #include #include -static_assert(std::is_same::value or std::is_same::value, "Internal error"); - -using namespace std::string_literals; +#include +#include +#include namespace h5 { - //static_assert(std::is_same<::hid_t, hid_t>::value, "Internal error"); - group::group(file f) : object(), parent_file(f) { id = H5Gopen2(f, "/", H5P_DEFAULT); - if (id < 0) throw std::runtime_error("Cannot open the root group / in the file " + f.name()); + if (id < 0) throw std::runtime_error("Error in h5::group: Opening the root group / for the file " + f.name() + " failed"); } - //group::group(object obj) : object(std::move(obj)) { - //if (!H5Iis_valid(this->id)) throw std::runtime_error("Invalid input in group constructor from id"); - //if (H5Iget_type(this->id) != H5I_GROUP) throw std::runtime_error("Group constructor must take the id of a group or a file "); - //} - - //group::group(hid_t id_) : group(object(id_)) {} - + // same function is used in h5::file std::string group::name() const { - char _n[1]; - ssize_t size = H5Iget_name(id, _n, 1); // first call, get the size only + // get length of the name + ssize_t size = H5Iget_name(id, nullptr, 1); + + // reserve a buffer and get name std::vector buf(size + 1, 0x00); - H5Iget_name(id, buf.data(), size + 1); // now get the name + H5Iget_name(id, buf.data(), size + 1); + + // return string std::string res = ""; res.append(&(buf.front())); return res; @@ -54,138 +48,160 @@ namespace h5 { bool group::has_key(std::string const &key) const { return H5Lexists(id, key.c_str(), H5P_DEFAULT); } bool group::has_subgroup(std::string const &key) const { + // check if a link with the given name exists if (!has_key(key)) return false; + + // check if the link can be opened hid_t id_node = H5Oopen(id, key.c_str(), H5P_DEFAULT); if (id_node <= 0) return false; + + // check if the opened link is actually a group bool r = (H5Iget_type(id_node) == H5I_GROUP); H5Oclose(id_node); return r; } bool group::has_dataset(std::string const &key) const { + // check if a link with the given name exists if (!has_key(key)) return false; + + // check if the link can be opened hid_t id_node = H5Oopen(id, key.c_str(), H5P_DEFAULT); if (id_node <= 0) return false; + + // check if the opened link is actually a dataset bool r = (H5Iget_type(id_node) == H5I_DATASET); H5Oclose(id_node); return r; } void group::unlink(std::string const &key, bool error_if_absent) const { + // check if a link with the given name exists if (!has_key(key)) { - if (error_if_absent) throw std::runtime_error("The key " + key + " is not present in the group " + name()); + // throw an exception if `error_if_absent` is true + if (error_if_absent) throw std::runtime_error("Error in h5::group: " + key + " does not exist in the group " + name()); return; } - //auto err = H5Gunlink(id, key.c_str()); // deprecated function + + // remove the link from the group auto err = H5Ldelete(id, key.c_str(), H5P_DEFAULT); - if (err < 0) throw std::runtime_error("Cannot unlink object " + key + " in group " + name()); + if (err < 0) throw std::runtime_error("Error in h5::group: Unlinking " + key + " in the group " + name() + " failed"); } group group::open_group(std::string const &key) const { + // return the current group if the key is empty if (key.empty()) return *this; - if (!has_key(key)) throw std::runtime_error("no subgroup " + key + " in the group"); - object sg = H5Gopen2(id, key.c_str(), H5P_DEFAULT); - if (sg < 0) throw std::runtime_error("Error in opening the subgroup " + key); - return {sg, parent_file}; + + // check if a link with the key exists + if (!has_key(key)) throw std::runtime_error("Error in h5::group: " + key + " does not exist in the group " + name()); + + // open the subgroup + object obj = H5Gopen2(id, key.c_str(), H5P_DEFAULT); + if (obj < 0) throw std::runtime_error("Error in h5::group: Opening the subgroup " + key + " in the group " + name() + " failed"); + return {obj, parent_file}; } group group::create_group(std::string const &key, bool delete_if_exists) const { + // return the current group if the key is empty if (key.empty()) return *this; + + // unlink existing group if 'delete_if_exists' is true if (delete_if_exists) unlink(key); + + // create new subgroup object obj = H5Gcreate2(id, key.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - if (not obj.is_valid()) throw std::runtime_error("Cannot create the subgroup " + key + " of the group " + name()); + if (not obj.is_valid()) throw std::runtime_error("Error in h5::group: Creating the subgroup " + key + " in the group " + name() + " failed"); return {obj, parent_file}; } void group::create_softlink(std::string const &target_key, std::string const &key, bool delete_if_exists) const { + // do nothing if the key or target key is empty if (target_key.empty() || key.empty()) return; - if (!has_key(target_key)) throw std::runtime_error("The target key " + target_key + " does not exist in group " + name()); + + // check if target exists + if (!has_key(target_key)) throw std::runtime_error("Error in h5::group: " + target_key + " does not exist in the group " + name()); + + // unlink existing link or throw exception if (delete_if_exists) unlink(key, false); else if (has_key(key)) - throw std::runtime_error("The key " + key + " already exists in group " + name()); + throw std::runtime_error("Error in h5::group: " + key + " already exists in the group " + name()); + + // create softlink auto const err = H5Lcreate_soft(target_key.c_str(), id, key.c_str(), H5P_DEFAULT, H5P_DEFAULT); - if (err < 0) throw std::runtime_error("Cannot create softlink " + target_key + " <- " + key); + if (err < 0) throw std::runtime_error("Error in h5::group: Creating the softlink " + key + " -> " + target_key + " failed"); } - /// Open an existing DataSet. Throw if it does not exist. dataset group::open_dataset(std::string const &key) const { - if (!has_key(key)) throw std::runtime_error("no dataset " + key + " in the group"); + // check if a link with the given name exists + if (!has_key(key)) throw std::runtime_error("Error in h5::group: " + key + " does not exist in the group " + name()); + + // open the dataset dataset ds = H5Dopen2(id, key.c_str(), H5P_DEFAULT); - if (!ds.is_valid()) throw std::runtime_error("Cannot open dataset " + key + " in the group " + name()); + if (!ds.is_valid()) throw std::runtime_error("Error in h5::group: Opening the dataset " + key + " in the group " + name() + " failed"); return ds; } - /** - * \brief Create a dataset. - * \param key The name of the subgroup - * - * NB : It unlinks the dataset if it exists. - */ dataset group::create_dataset(std::string const &key, datatype ty, dataspace sp, hid_t pl) const { + // unlink existing link unlink(key); + + // create new dataset dataset ds = H5Dcreate2(id, key.c_str(), ty, sp, H5P_DEFAULT, pl, H5P_DEFAULT); - if (!ds.is_valid()) throw std::runtime_error("Cannot create the dataset " + key + " in the group " + name()); + if (!ds.is_valid()) throw std::runtime_error("Error in h5::group: Creating the dataset " + key + " in the group " + name() + " failed"); return ds; } dataset group::create_dataset(std::string const &key, datatype ty, dataspace sp) const { return create_dataset(key, ty, sp, H5P_DEFAULT); } - //---------------------------------------------------------- - // Keep as an example of H5LTset_attribute_string - /* - void group::write_string_attribute (std::string const & obj_name, std::string const & attr_name, std::string const & value){ - herr_t err = H5LTset_attribute_string(id, obj_name.c_str(), attr_name.c_str(), value.c_str()); - if (err<0) throw std::runtime_error( "Error in setting attribute of "+ obj_name+" named "+ attr_name + " to " + value); - } -*/ - //----------------------------------------------------------------------- - // C callbacks for the next functions using H5Literate extern "C" { + herr_t get_group_elements_name_ds(::hid_t loc_id, const char *name, const H5L_info_t *, void *opdata) { H5O_info_t object_info; herr_t err = H5Oget_info_by_name(loc_id, name, &object_info, H5P_DEFAULT); - if (err < 0) throw std::runtime_error("get_group_elements_name_ds internal"); + if (err < 0) throw std::runtime_error("Error in h5::get_group_elements_name_ds: H5Oget_info_by_name call failed"); if (object_info.type == H5O_TYPE_DATASET) static_cast *>(opdata)->push_back(name); return 0; } + herr_t get_group_elements_name_grp(::hid_t loc_id, const char *name, const H5L_info_t *, void *opdata) { H5O_info_t object_info; herr_t err = H5Oget_info_by_name(loc_id, name, &object_info, H5P_DEFAULT); - if (err < 0) throw std::runtime_error("get_group_elements_name_grp internal"); + if (err < 0) throw std::runtime_error("Error in h5::get_group_elements_name_grp: H5Oget_info_by_name call failed"); if (object_info.type == H5O_TYPE_GROUP) static_cast *>(opdata)->push_back(name); return 0; } + herr_t get_group_elements_name_ds_grp(::hid_t loc_id, const char *name, const H5L_info_t *, void *opdata) { H5O_info_t object_info; herr_t err = H5Oget_info_by_name(loc_id, name, &object_info, H5P_DEFAULT); - if (err < 0) throw std::runtime_error("get_group_elements_name_grp internal"); + if (err < 0) throw std::runtime_error("Error in h5::get_group_elements_name_ds_grp: H5Oget_info_by_name call failed"); if ((object_info.type == H5O_TYPE_GROUP) or (object_info.type == H5O_TYPE_DATASET)) static_cast *>(opdata)->push_back(name); return 0; } - } - //----------------------------------------------------------------------- + + } // extern "C" std::vector group::get_all_subgroup_names() const { std::vector grp_name; int r = H5Literate(::hid_t(id), H5_INDEX_NAME, H5_ITER_NATIVE, nullptr, get_group_elements_name_grp, static_cast(&grp_name)); - if (r != 0) throw std::runtime_error("Iteration over subgroups of group " + name() + "failed"); + if (r != 0) throw std::runtime_error("Error in h5::group: Iterating over subgroups of the group " + name() + "failed"); return grp_name; } std::vector group::get_all_dataset_names() const { std::vector ds_name; int r = H5Literate(::hid_t(id), H5_INDEX_NAME, H5_ITER_NATIVE, nullptr, get_group_elements_name_ds, static_cast(&ds_name)); - if (r != 0) throw std::runtime_error("Iteration over datasets of group " + name() + "failed"); + if (r != 0) throw std::runtime_error("Error in h5::group: Iterating over datasets of the group " + name() + "failed"); return ds_name; } std::vector group::get_all_subgroup_dataset_names() const { std::vector ds_name; int r = H5Literate(::hid_t(id), H5_INDEX_NAME, H5_ITER_NATIVE, nullptr, get_group_elements_name_ds_grp, static_cast(&ds_name)); - if (r != 0) throw std::runtime_error("Iteration over datasets of group " + name() + "failed"); + if (r != 0) throw std::runtime_error("Error in h5::group: Iterating over datasets and subgroups of the group " + name() + "failed"); return ds_name; } diff --git a/c++/h5/group.hpp b/c++/h5/group.hpp index e5b7bc62..d251f3d0 100644 --- a/c++/h5/group.hpp +++ b/c++/h5/group.hpp @@ -14,131 +14,181 @@ // // Authors: Henri Menke, Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a handle to an HDF5 group and various methods to simplify the creation/opening of + * subgroups, datasets and softlinks within a group. + */ + #ifndef LIBH5_GROUP_HPP #define LIBH5_GROUP_HPP -#include - #include "./file.hpp" +#include +#include +#include + namespace h5 { /** - * HDF5 group + * @brief A handle to an HDF5 group. + * + * @details This class inherits from the general h5::object class. Each group stores the parent h5::file + * to which it belongs. + * + * It provides various methods to simplify the creation of new and opening of existing groups, subgroups, + * datasets and softlinks within the current group. */ class group : public object { - + // File to which the group belongs. file parent_file; public: - group() = default; // for python converter only + /// Default constructor (only necessary for the Python interface). + group() = default; - /// Takes the "/" group at the top of the file + /** + * @brief Constructor to open the root ("/") group in the given file. + * @param f h5::file. + */ group(file f); private: - // construct from the bare object and the parent - // internal use only for open/create subgroup - group(object obj, file _parent_file) : object{obj}, parent_file(std::move(_parent_file)) {} + // Copy the given object and parent file (only used internally to open/create a subgroup). + group(object obj, file _parent_file) : object{obj}, parent_file{std::move(_parent_file)} {} public: - /// Name of the group + /// Get the name of the group. [[nodiscard]] std::string name() const; - /// Access to the parent file + /// Get the parent file to which the group belongs. [[nodiscard]] file get_file() const { return parent_file; } /** - * True iff key is an object in the group + * @brief Check if a link with the given key exists in the group. * - * @param key + * @param key Name of the link. + * @return True if the link exists, false otherwise. */ [[nodiscard]] bool has_key(std::string const &key) const; /** - * True iff key is a subgroup of this. + * @brief Check if a subgroup with the given key exists in the group and is accessible. * - * @param key + * @param key Name of the subgroup. + * @return True if the subgroup exists and can be accessed, false otherwise. */ [[nodiscard]] bool has_subgroup(std::string const &key) const; /** - * True iff key is a dataset of this. + * @brief Check if a dataset with the given key exists in the group and is accessible. * - * @param key + * @param key Name of the dataset. + * @return True if the dataset exists, false otherwise. */ [[nodiscard]] bool has_dataset(std::string const &key) const; /** - * Unlinks the subgroup key if it exists - * No error is thrown if key does not exists - * NB : unlink is almost a remove, but it does not really remove from the file (memory is not freed). - * After unlinking a large object, a h5repack may be needed. Cf HDF5 documentation. + * @brief Remove a link with the given key from the group. * - * @param key The name of the subgroup to be removed. - * @param error_if_absent If True, throws an error if the key is missing. + * @details It simply calls `H5Ldelete` to delete the link. If the given link does not exist, it throws + * an exception if `error_if_absent == true`, otherwise it does nothing. + * + * @param key Name of the link to be removed. + * @param error_if_absent If true, throws an exception if the key is not the name of a link in the group. */ void unlink(std::string const &key, bool error_if_absent = false) const; /** - * Open a subgroup. - * Throws std::runtime_error if it does not exist. + * @brief Open a subgroup with the given key in the group. + * + * @details If the given key is empty, a handle to the current group is returned. Throws an exception if the + * subgroup fails to be opened. * - * @param key The name of the subgroup. If empty, return this group + * @param key Name of the subgroup. + * @return A handle to the opened subgroup. */ [[nodiscard]] group open_group(std::string const &key) const; /** - * Create a subgroup in this group - * - * @param key The name of the subgroup. If empty, return this group. - * @param delete_if_exists Unlink the group if it exists + * @brief Create a subgroup with the given key in the group. + * + * @details If a subgroup with the given key already exists, it is unlinked first if `delete_if_exists == true`. + * If the given key is empty, a handle to the current group is returned. Throws an exception if the subgroup fails + * to be created. + * + * @param key Name of the subgroup to be created. + * @param delete_if_exists If true, unlink first an existing subgroup with the same name. + * @return A handle to the created subgroup. */ - group create_group(std::string const &key, bool delete_if_exists = true) const; // NOLINT + [[nodiscard]] group create_group(std::string const &key, bool delete_if_exists = true) const; /** - * Create a softlink in this group - * - * @param target_key The path the link should point to. Has to exist. - * @param key The link name that will point to the target path. - * @param delete_if_exists Unlink the key first if it exists. + * @brief Create a softlink with the given key to a target with a given target key in this group. + * + * @details Does nothing if the key or target key is empty. If `delete_if_exists == true`, it first unlinks + * an existing link with the same name. Throws an exception if the target does not exist, if a link with + * the given key already exists and `delete_if_exists == false`, or if the softlink fails to be created. + * + * @param target_key Name of target. + * @param key Name of the softlink to be created. + * @param delete_if_exists If true, unlink first an existing key with the same name. */ void create_softlink(std::string const &target_key, std::string const &key, bool delete_if_exists = true) const; /** - * Open a existing DataSet in the group. - * Throws std::runtime_error if it does not exist. + * @brief Open a dataset with the given key in the group. + * + * @details Throws an exception if there exists no link with the given key or if the dataset fails to be opened. * - * @param key The name of the subgroup. If empty, return this group + * @param key Name of the dataset. + * @return A handle to the opened dataset. */ [[nodiscard]] dataset open_dataset(std::string const &key) const; /** - * Create a dataset in this group - * - * @param key The name of the dataset. - * @param ty Datatype - * @param sp Dataspace + * @brief Create a dataset with the given key, datatype, dataspace and dataset creation property list in this group. + * + * @details It first unlinks an existing dataset with the same name. Throws an exception if the dataset fails to be + * created. + * + * @param key Name of the dataset to be created. + * @param ty h5::datatype. + * @param sp h5::dataspace. + * @param pl Dataset creation property list. + * @return A handle to the created dataset. */ - [[nodiscard]] dataset create_dataset(std::string const &key, datatype ty, dataspace sp) const; + [[nodiscard]] dataset create_dataset(std::string const &key, datatype ty, dataspace sp, hid_t pl) const; /** - * Create a dataset in this group - * - * @param key The name of the dataset. - * @param ty Datatype - * @param sp Dataspace - * @param pl Property list + * @brief Create a dataset with the given key, datatype and dataspace in this group. + * + * @details It simply calls group::create_dateset with the default dataset creation property list. + * + * @param key Name of the dataset to be created. + * @param ty h5::datatype. + * @param sp h5::dataspace. + * @return A handle to the created dataset. */ - [[nodiscard]] dataset create_dataset(std::string const &key, datatype ty, dataspace sp, hid_t pl) const; + [[nodiscard]] dataset create_dataset(std::string const &key, datatype ty, dataspace sp) const; - /// Returns all names of subgroup of G + /** + * @brief Get all the names of the subgroups in the current group. + * @return A vector with the names of all the subgroups. + */ [[nodiscard]] std::vector get_all_subgroup_names() const; - /// Returns all names of dataset of G + /** + * @brief Get all the names of the datasets in the current group. + * @return A vector with the names of all the datasets. + */ [[nodiscard]] std::vector get_all_dataset_names() const; - /// Returns all names of dataset of G + /** + * @brief Get all the names of the subgroups and datasets in the current group. + * @return A vector with the names of all the subgroups and datasets. + */ [[nodiscard]] std::vector get_all_subgroup_dataset_names() const; }; diff --git a/c++/h5/h5.hpp b/c++/h5/h5.hpp index 84b05057..4336e5df 100644 --- a/c++/h5/h5.hpp +++ b/c++/h5/h5.hpp @@ -14,6 +14,11 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Includes all relevant h5 headers. + */ + #ifndef LIBH5_H5_HPP #define LIBH5_H5_HPP @@ -38,29 +43,29 @@ // in some old version of hdf5 (Ubuntu 12.04 e.g.), the macro is not yet defined. #ifndef H5_VERSION_GE - #define H5_VERSION_GE(Maj, Min, Rel) \ (((H5_VERS_MAJOR == Maj) && (H5_VERS_MINOR == Min) && (H5_VERS_RELEASE >= Rel)) || ((H5_VERS_MAJOR == Maj) && (H5_VERS_MINOR > Min)) \ || (H5_VERS_MAJOR > Maj)) - #endif namespace h5 { + /** + * @brief Concept to check if a type can be read/written from/to HDF5. + * @tparam T Type to check. + */ template - concept Storable = requires(T const &xc, T &x, h5::group g, std::string const &subgroup_name) { + concept Storable = requires(T const &xc, T &x, h5::group g, std::string const &name) { { T::hdf5_format() } -> std::convertible_to; - //{ get_hdf5_format(xc) } -> std::convertible_to; - { h5_write(g, subgroup_name, xc) }; - { h5_read(g, subgroup_name, x) }; + { h5_write(g, name, xc) }; + { h5_read(g, name, x) }; }; } // namespace h5 -// Python wrapping declaration +// Python wrapping declaration #ifdef C2PY_INCLUDED #include
#endif - #endif // LIBH5_H5_HPP diff --git a/c++/h5/macros.hpp b/c++/h5/macros.hpp index 65f97d73..833ffd64 100644 --- a/c++/h5/macros.hpp +++ b/c++/h5/macros.hpp @@ -14,30 +14,40 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Macros used in the h5 library. + */ + #ifndef LIBH5_MACROS_HPP #define LIBH5_MACROS_HPP #include #include -// Basic macros for the implementation +// ---------------- Stringify ---------------- #define H5_AS_STRING(...) H5_AS_STRING2(__VA_ARGS__) #define H5_AS_STRING2(...) #__VA_ARGS__ -// The REQUIRES +// ---------------- Requires ---------------- + #ifdef __clang__ #define H5_REQUIRES(...) __attribute__((enable_if(__VA_ARGS__, H5_AS_STRING2(__VA_ARGS__)))) #elif __GNUC__ #define H5_REQUIRES(...) requires(__VA_ARGS__) #endif -// DEBUG PRINTING +// ---------------- Print ---------------- + #define H5_PRINT(X) std::cerr << H5_AS_STRING(X) << " = " << X << " at " << __FILE__ << ":" << __LINE__ << '\n' +// ---------------- Inline ---------------- + #define H5_FORCEINLINE __inline__ __attribute__((always_inline)) -// CONTRACTS like +// ---------------- Debugging ---------------- + #define H5_EXPECTS(X) \ if (!(X)) { \ std::cerr << "Precondition " << H5_AS_STRING(X) << " violated at " << __FILE__ << ":" << __LINE__ << "\n"; \ diff --git a/c++/h5/object.cpp b/c++/h5/object.cpp index 0fade629..4385fdbb 100644 --- a/c++/h5/object.cpp +++ b/c++/h5/object.cpp @@ -14,52 +14,127 @@ // // Authors: Olivier Parcollet, Nils Wentzell -#include +#include "./macros.hpp" +#include "./object.hpp" #include #include #include #include -// FIXME -//static_assert(std::is_same_v, "Configuration error in HDF5. Check version."); - -#include "./object.hpp" -#include #include +#include +#include #include +#include +#include + +// make sure that hid_t and hsize_t have a compatible type in the HDF5 library +static_assert(std::is_same_v or std::is_same_v, "h5::hid_t type is not compatible with HDF5"); +static_assert(sizeof(::hsize_t) == sizeof(h5::hsize_t), "h5::hsize_t type is not compatible with HDF5"); namespace h5 { - namespace detail { + // anonymous namespace for internal functions + namespace { + + // decrease reference count if HDF5 ID is valid + inline void xdecref(hid_t id) { + if (H5Iis_valid(id)) H5Idec_ref(id); + } + + // increase reference count if HDF5 ID is valid + inline void xincref(hid_t id) { + if (H5Iis_valid(id)) H5Iinc_ref(id); + } + + // stores an HDF5 datatype and its name + struct h5_name_t { + datatype hdf5_type; + std::string name; + }; + + // table of HDF5 datatypes and their names + std::vector h5_name_table; // NOLINT (global variable is in an unnamed namespace) + + // initialize the table of HDF5 datatypes and their names + void init_h5_name_table() { + h5_name_table = std::vector{ + {hdf5_type(), H5_AS_STRING(char)}, + {hdf5_type(), H5_AS_STRING(signed char)}, + {hdf5_type(), H5_AS_STRING(unsigned char)}, + {hdf5_type(), H5_AS_STRING(bool)}, + {hdf5_type(), H5_AS_STRING(short)}, + {hdf5_type(), H5_AS_STRING(unsigned short)}, + {hdf5_type(), H5_AS_STRING(int)}, + {hdf5_type(), H5_AS_STRING(unsigned int)}, + {hdf5_type(), H5_AS_STRING(long)}, + {hdf5_type(), H5_AS_STRING(unsigned long)}, + {hdf5_type(), H5_AS_STRING(long long)}, + {hdf5_type(), H5_AS_STRING(unsigned long long)}, + {hdf5_type(), H5_AS_STRING(float)}, + {hdf5_type(), H5_AS_STRING(double)}, + {hdf5_type(), H5_AS_STRING(long double)}, + {hdf5_type>(), H5_AS_STRING(std::complex)}, + {hdf5_type>(), H5_AS_STRING(std::complex)}, + {hdf5_type>(), H5_AS_STRING(std::complex)}, + {hdf5_type(), H5_AS_STRING(std::string)}, + {hdf5_type(), "Complex Compound Datatype"} // + }; + } + + } // namespace + + object object::from_borrowed(hid_t id) { + xincref(id); + return {id}; + } + + object::object(object const &x) : id(x.id) { xincref(id); } + + object &object::operator=(object &&x) noexcept { + xdecref(id); + id = x.id; + x.id = 0; + return *this; + } + + void object::close() { + xdecref(id); + id = 0; + } + + int object::get_ref_count() const { return H5Iget_ref(id); } - //static_assert(std::is_same<::hid_t, hid_t>::value, "Internal error"); - static_assert(sizeof(::hsize_t) == sizeof(hsize_t), "Mismatch in bitsize of h5::hsize_t and hsize_t"); + bool object::is_valid() const { return H5Iis_valid(id) == 1; } + + namespace detail { - // specializations for all basic types // clang-format off + // map basic C++ types to HDF5 datatypes template <> hid_t hid_t_of (){return H5T_NATIVE_CHAR;} template <> hid_t hid_t_of (){return H5T_NATIVE_SCHAR;} template <> hid_t hid_t_of (){return H5T_NATIVE_UCHAR;} - + template <> hid_t hid_t_of (){return H5T_NATIVE_SHORT;} template <> hid_t hid_t_of (){return H5T_NATIVE_INT;} template <> hid_t hid_t_of (){return H5T_NATIVE_LONG;} template <> hid_t hid_t_of (){return H5T_NATIVE_LLONG;} - + template <> hid_t hid_t_of (){return H5T_NATIVE_USHORT;} template <> hid_t hid_t_of (){return H5T_NATIVE_UINT;} template <> hid_t hid_t_of (){return H5T_NATIVE_ULONG;} template <> hid_t hid_t_of (){return H5T_NATIVE_ULLONG;} - + template <> hid_t hid_t_of (){return H5T_NATIVE_FLOAT;} template <> hid_t hid_t_of (){return H5T_NATIVE_DOUBLE;} template <> hid_t hid_t_of (){return H5T_NATIVE_LDOUBLE;} - + template <> hid_t hid_t_of> (){return H5T_NATIVE_FLOAT;} template <> hid_t hid_t_of> (){return H5T_NATIVE_DOUBLE;} template <> hid_t hid_t_of> (){return H5T_NATIVE_LDOUBLE;} + // custom HDF5 datatype for strings hid_t const str_dt = [](){ hid_t dt = H5Tcopy(H5T_C_S1); H5Tset_size(dt, H5T_VARIABLE); @@ -67,11 +142,13 @@ namespace h5 { H5Tlock(dt); return dt; }(); - + + // map different string types to the custom HDF5 string datatype from above template <> hid_t hid_t_of (){return detail::str_dt;} template <> hid_t hid_t_of (){return detail::str_dt;} template <> hid_t hid_t_of (){return detail::str_dt;} - + + // custom HDF5 datatype for a complex number hid_t const cplx_cmpd_dt = [](){ hid_t dt = H5Tcreate(H5T_COMPOUND, 16); H5Tinsert(dt, "r", 0, H5T_NATIVE_DOUBLE); @@ -79,10 +156,12 @@ namespace h5 { H5Tlock(dt); return dt; }(); + + // map the custom complex type to the custom HDF5 datatype from above template <> hid_t hid_t_of (){return detail::cplx_cmpd_dt;} // clang-format on - // bool. Use a lambda to initialize it. + // custom HDF5 datatype for bool (use a lambda to initialize it?) template <> hid_t hid_t_of() { hid_t bool_enum_h5type = H5Tenum_create(H5T_NATIVE_CHAR); @@ -91,103 +170,32 @@ namespace h5 { H5Tenum_insert(bool_enum_h5type, "TRUE", (val = 1, &val)); return bool_enum_h5type; } - } // namespace detail - - // ----------------------- name --------------------------- - - struct h5_name_t { - datatype hdf5_type; // type in hdf5 - std::string name; // name of the type - }; - - //--------- - - std::vector h5_name_table; - - //---------- - - static void init_h5_name_table() { - h5_name_table = std::vector{ - {hdf5_type(), H5_AS_STRING(char)}, - {hdf5_type(), H5_AS_STRING(signed char)}, - {hdf5_type(), H5_AS_STRING(unsigned char)}, - {hdf5_type(), H5_AS_STRING(bool)}, - {hdf5_type(), H5_AS_STRING(short)}, - {hdf5_type(), H5_AS_STRING(unsigned short)}, - {hdf5_type(), H5_AS_STRING(int)}, - {hdf5_type(), H5_AS_STRING(unsigned int)}, - {hdf5_type(), H5_AS_STRING(long)}, - {hdf5_type(), H5_AS_STRING(unsigned long)}, - {hdf5_type(), H5_AS_STRING(long long)}, - {hdf5_type(), H5_AS_STRING(unsigned long long)}, - {hdf5_type(), H5_AS_STRING(float)}, - {hdf5_type(), H5_AS_STRING(double)}, - {hdf5_type(), H5_AS_STRING(long double)}, - {hdf5_type>(), H5_AS_STRING(std::complex)}, - {hdf5_type>(), H5_AS_STRING(std::complex)}, - {hdf5_type>(), H5_AS_STRING(std::complex)}, - {hdf5_type(), H5_AS_STRING(std::string)}, - {hdf5_type(), "Complex Compound Datatype"} // - }; - } - - //-------- - - object get_hdf5_type(dataset ds) { return H5Dget_type(ds); } - - bool hdf5_type_equal(datatype dt1, datatype dt2) { - // For string do not compare size, cset.. - if (H5Tget_class(dt1) == H5T_STRING) { return H5Tget_class(dt2) == H5T_STRING; } - auto res = H5Tequal(dt1, dt2); - if (res < 0) { throw std::runtime_error("Failure it hdf5 type comparison"); } - return res > 0; - } - std::string get_name_of_h5_type(datatype t) { + } // namespace detail + std::string get_name_of_h5_type(datatype dt) { + // initialize name table if it has not been done yet if (h5_name_table.empty()) init_h5_name_table(); - auto _end = h5_name_table.end(); - auto pos = std::find_if(h5_name_table.begin(), _end, [t](auto const &x) { return hdf5_type_equal(t, x.hdf5_type); }); - if (pos == _end) throw std::logic_error("HDF5/Python : impossible error"); - return pos->name; - } - - // ----------------------- Reference counting --------------------------- - // xdecref, xincref manipulate the the ref, but ignore invalid (incl. 0) id. - // like XINC_REF and XDEC_REF in python - inline void xdecref(hid_t id) { - if (H5Iis_valid(id)) H5Idec_ref(id); - } + // find name in table + auto _end = h5_name_table.end(); + auto pos = std::find_if(h5_name_table.begin(), _end, [dt](auto const &x) { return hdf5_type_equal(dt, x.hdf5_type); }); - inline void xincref(hid_t id) { - if (H5Iis_valid(id)) H5Iinc_ref(id); + // return name if found, otherwise throw an exception + if (pos == _end) throw std::logic_error("Error in h5::get_name_of_h5_type: datatype not supported"); + return pos->name; } - // ----------------------- object --------------------------- - - object::object(object const &x) : id(x.id) { xincref(id); } // a new copy, a new ref. + datatype get_hdf5_type(dataset ds) { return H5Dget_type(ds); } - // make an object when the id is now owned (simply inc. the ref). - object object::from_borrowed(hid_t id) { - xincref(id); - return {id}; - } + bool hdf5_type_equal(datatype dt1, datatype dt2) { + // for strings check only if they are both of the class H5T_STRING + if (H5Tget_class(dt1) == H5T_STRING) { return H5Tget_class(dt2) == H5T_STRING; } - object &object::operator=(object &&x) noexcept { //steals the ref, after properly decref its own. - xdecref(id); - id = x.id; - x.id = 0; - return *this; + // for other types use H5Tequal + auto res = H5Tequal(dt1, dt2); + if (res < 0) { throw std::runtime_error("Error in h5::hdf5_type_equal: H5Tequal call failed"); } + return res > 0; } - void object::close() { - xdecref(id); - id = 0; - } // e.g. to close a file explicitely. - - int object::get_ref_count() const { return H5Iget_ref(id); } - - bool object::is_valid() const { return H5Iis_valid(id) == 1; } - } // namespace h5 diff --git a/c++/h5/object.hpp b/c++/h5/object.hpp index 89d3a77f..9222fc45 100644 --- a/c++/h5/object.hpp +++ b/c++/h5/object.hpp @@ -14,136 +14,237 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a generic handle for HDF5 objects. + */ + #ifndef LIBH5_OBJECT_HPP #define LIBH5_OBJECT_HPP #include #include -#include +#include #include -#include "./macros.hpp" +#include +#include +#include namespace h5 { - // We copy this from hdf5.h, and static_assert its validity in the cpp - // in order to completely isolate our header from the hdf5 headers - // Hence complex installation paths to hdf5 are only needed in the cpp file, - // not by the users of the library. + /** + * @brief ID type used in HDF5. + * + * @details This is just a copy from the HDF5 library (see the official + * documentation). + * It is used to completely isolate our header from the HDF5 headers. In the .cpp file a `static_assert` is used to verify + * its validity. + */ using hid_t = int64_t; - using hsize_t = + + /** + * @brief Size type used in HDF5. + * @details This is just a copy from the HDF5 library. It is used to completely isolate our header from the HDF5 headers. + * In the .cpp file a `static_assert` is used to verify its validity. + */ #ifdef H5_VER_GE_113 - uint64_t; + using hsize_t = uint64_t; #else - unsigned long long; + using hsize_t = unsigned long long; #endif + + /// Vector of h5::hsize_t used throughout the h5 library. using v_t = std::vector; - // A complex compound type consisting of two doubles - // This type is stored and loaded as an hdf5 compound datatype + /** + * @brief A complex compound type consisting of two doubles to represent a complex number. + * @details This type can be used to read/write complex numbers from/to HDF5 files. + */ struct dcplx_t { - double r, i; + /// Real part. + double r; + + /// Imaginary part. + double i; }; - // impl trait to detect complex numbers + // Type trait to check if a type is std::complex. template struct _is_complex : std::false_type {}; + // Specialization of h5::_is_complex for std::complex. template struct _is_complex> : std::true_type {}; + /** + * @brief Boolean type trait set to true for std::complex types. + * @tparam T Type to check. + */ template constexpr bool is_complex_v = _is_complex::value; - // impl - template - std::runtime_error make_runtime_error(T const &...x) { - std::stringstream fs; - (fs << ... << x); - return std::runtime_error{fs.str()}; + /** + * @brief Create a std::runtime_error with an error message constructed from the given arguments. + * + * @tparam Ts Types of the arguments. + * @param ts Arguments streamed into the error message string. + * @return std::runtime_error. + */ + template + [[nodiscard]] std::runtime_error make_runtime_error(Ts const &...ts) { + std::stringstream ss; + (ss << ... << ts); + return std::runtime_error{ss.str()}; } - /* - * A handle to the a general HDF5 object + /** + * @brief A generic handle for HDF5 objects. + * + * @details It simply stores the h5::hid_t of the corresponding HDF5 object. + * + * More specific HDF5 objects, like h5::file or h5::group, inherit from this class. Since it lacks a virtual + * destructor, the derived classes should not be deleted through a pointer to this class. It is recommended + * to use the derived classes whenever possible. * - * HDF5 uses a reference counting system, similar to python. - * h5::object handles the proper reference couting (similar to pybind11::object e.g.) - * using a RAII pattern (hence exception safety). + * HDF5's reference counting system is similar to python. This class handles the proper reference counting + * using a RAII pattern (hence exception safe). Depending on how the object is constructed, it either + * increases the reference count associated HDF5 object or steals it. */ class object { - protected: - hid_t id = 0; //NOLINT Ok, I want a protected variable ... + // Wrapped HDF5 ID. + hid_t id = 0; // NOLINT (protected member is wanted here) public: - /// make an h5::object from a simple borrowed ref (simply inc. the ref). - static object from_borrowed(hid_t id); - - /// Constructor from an owned id (or 0). It steals (take ownership) of the reference. + /** + * @brief Create an h5::object for a given HDF5 ID and increase its reference count. + * + * @param id HDF5 ID. + * @return h5::object. + */ + [[nodiscard]] static object from_borrowed(hid_t id); + + /** + * @brief Construct a new h5::object for a given HDF5 ID by taking ownership, i.e. without increasing the + * reference count. + * + * @details If no ID is given, it is set to zero (default). + * + * @param id HDF5 ID. + */ object(hid_t id = 0) : id(id) {} - /// A new ref. No deep copy. + /** + * @brief Copy constructor copies the underlying HDF5 ID and increases its reference count. + * @param x Object to copy. + */ object(object const &x); - /// Steals the reference + /** + * @brief Move constructor steals the underlying HDF5 ID without increasing its reference count. + * @param x Object to move. + */ object(object &&x) noexcept : id(x.id) { x.id = 0; } - /// Copy the reference and incref + /** + * @brief Copy assignment operator copies the underlying HDF5 ID and increases its reference count. + * @param x Object to copy. + */ object &operator=(object const &x) { operator=(object(x)); return *this; } - /// Steals the ref. + /** + * @brief Move assignment operator steals the underlying HDF5 ID without increasing its reference count. + * @param x Object to move. + */ object &operator=(object &&x) noexcept; - /// + /// Destructor decreases the reference count and sets the object's ID to zero. ~object() { close(); } - /// Release the HDF5 handle and reset the object to default state (id =0). + /// Release the HDF5 handle by decreasing the reference count and by setting the object's ID to zero. void close(); - /// cast operator to use it in the C function as its id + /// User-defined conversion to h5::hid_t. operator hid_t() const { return id; } - /// Get the current number of references + /// Get the current reference count. [[nodiscard]] int get_ref_count() const; - /// Ensure the id is valid (by H5Iis_valid). + /// Ensure that the wrapped HDF5 ID is valid (by calling `H5Iis_valid`). [[nodiscard]] bool is_valid() const; }; - //----------------------------- - // simple but useful aliases. It does NOT check the h5 type of the object. // FIXME : derive and make a check ?? - using dataset = object; - using datatype = object; + + /// Type alias for an HDF5 dataset. + using dataset = object; + + /// Type alias for an HDF5 datatype. + using datatype = object; + + /// Type alias for an HDF5 dataspace. using dataspace = object; - using proplist = object; + + /// Type alias for an HDF5 property list. + using proplist = object; + + /// Type alias for an HDF5 attribute. using attribute = object; - // ----------------------- hdf5_type --------------------------- - // Correspondance T -> hdf5 type namespace detail { + + // Map a C++ type to an HDF5 type (specializations are in object.cpp). template hid_t hid_t_of(); - } + } // namespace detail + + /** + * @brief Map a given C++ type to an HDF5 datatype. + * + * @tparam T C++ type. + * @return h5::datatype object corresponding to the given C++ type. + */ template - datatype hdf5_type() { + [[nodiscard]] datatype hdf5_type() { return object::from_borrowed(detail::hid_t_of()); } - // ------------------------------ - - // A function to get the name of a datatype in clear (for error messages) - std::string get_name_of_h5_type(datatype ty); + /** + * @brief Get the name of an h5::datatype (for error messages). + * + * @details Throws an exception if the datatype is not supported. + * + * @param dt h5::datatype. + * @return String representation of the datatype. + */ + [[nodiscard]] std::string get_name_of_h5_type(datatype dt); - // Get hdf5 type of a dataset - object get_hdf5_type(dataset); + /** + * @brief Get the HDF5 type stored in a given h5::dataset. + * + * @param ds h5::dataset. + * @return h5::datatype of the given h5::dataset. + */ + [[nodiscard]] datatype get_hdf5_type(dataset ds); - // Check equality of datatypes - bool hdf5_type_equal(datatype, datatype); + /** + * @brief Check two HDF5 datatypes for equality. + * + * @details For string types, this function only checks if they are both of the class `H5T_STRING`. + * It does not take into account the size, character set, etc. + * + * Otherwise, it simply uses `H5Tequal`. Throws an exception if the HDF5 call fails. + * + * @param dt1 h5::datatype #1. + * @param dt2 h5::datatype #2. + * @return True, if the two datatypes are equal. + */ + [[nodiscard]] bool hdf5_type_equal(datatype dt1, datatype dt2); } // namespace h5 diff --git a/c++/h5/scalar.hpp b/c++/h5/scalar.hpp index d3bbf8bd..5424af2d 100644 --- a/c++/h5/scalar.hpp +++ b/c++/h5/scalar.hpp @@ -14,32 +14,70 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides a generic interface to read/write scalars from/to HDF5. + */ + #ifndef LIBH5_SCALAR_HPP #define LIBH5_SCALAR_HPP -#include -#include "./group.hpp" #include "./array_interface.hpp" +#include "./group.hpp" +#include "./macros.hpp" +#include "./object.hpp" + +#include +#include +#include + namespace h5 { namespace array_interface { - template - h5_array_view h5_array_view_from_scalar(S &&s) { - return {hdf5_type>(), (void *)(&s), 0, is_complex_v>}; + /** + * @brief Create an array view for a scalar. + * + * @tparam T Scalar type. + * @param x Scalar value. + * @return h5::array_interface::h5_array_view of rank 0. + */ + template + h5_array_view h5_array_view_from_scalar(T &x) { + return {hdf5_type>(), (void *)(&x), 0, is_complex_v>}; } + } // namespace array_interface + /** + * @brief Write a scalar to an HDF5 dataset. + * + * @details The scalar type needs to be either arithmetic, complex or of type h5::dcplx_t. + * + * @tparam T Scalar type. + * @param g h5::group in which the dataset is created. + * @param name Name of the dataset. + * @param x Scalar value to be written. + */ template void h5_write(group g, std::string const &name, T const &x) H5_REQUIRES(std::is_arithmetic_v or is_complex_v or std::is_same_v) { array_interface::write(g, name, array_interface::h5_array_view_from_scalar(x), false); } + /** + * @brief Read a scalar from an HDF5 dataset. + * + * @details The scalar type needs to be either arithmetic, complex or of type h5::dcplx_t. + * + * @tparam T Scalar type. + * @param g h5::group containing the dataset. + * @param name Name of the dataset. + * @param x Scalar variable to be read into. + */ template void h5_read(group g, std::string const &name, T &x) H5_REQUIRES(std::is_arithmetic_v or is_complex_v or std::is_same_v) { - + // backward compatibility to read complex values stored the old way (in a subgroup) if constexpr (is_complex_v) { - // Backward compatibility to read complex stored the old way if (g.has_subgroup(name)) { group gr = g.open_group(name); H5_ASSERT(gr.has_key("r") and gr.has_key("i")); @@ -51,24 +89,46 @@ namespace h5 { } } + // get h5_lengths_type auto lt = array_interface::get_h5_lengths_type(g, name); + // read complex values stored as a compound HDF5 datatype if constexpr (is_complex_v) { - // Allow reading compound hdf5 dataype into complex if (hdf5_type_equal(lt.ty, hdf5_type())) { - h5_read(g, name, reinterpret_cast(x)); + h5_read(g, name, reinterpret_cast(x)); // NOLINT (reinterpret_cast is safe here) return; } } + // read scalar value array_interface::read(g, name, array_interface::h5_array_view_from_scalar(x), lt); } + /** + * @brief Write a scalar to an HDF5 attribute. + * + * @details The scalar type needs to be either arithmetic or std::complex. + * + * @tparam T Scalar type. + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param x Scalar value to be written. + */ template void h5_write_attribute(object obj, std::string const &name, T const &x) H5_REQUIRES(std::is_arithmetic_v or is_complex_v) { array_interface::write_attribute(obj, name, array_interface::h5_array_view_from_scalar(x)); } + /** + * @brief Read a scalar from an HDF5 attribute. + * + * @details The scalar type needs to be either arithmetic or std::complex. + * + * @tparam T Scalar type. + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param x Scalar variable to be read into. + */ template void h5_read_attribute(object obj, std::string const &name, T &x) H5_REQUIRES(std::is_arithmetic_v or is_complex_v) { array_interface::read_attribute(obj, name, array_interface::h5_array_view_from_scalar(x)); diff --git a/c++/h5/serialization.hpp b/c++/h5/serialization.hpp index 5acd6f0a..4cd9d37f 100644 --- a/c++/h5/serialization.hpp +++ b/c++/h5/serialization.hpp @@ -14,29 +14,53 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides generic serialize and deserialize functions for types that can be read/written from/to HDF5. + */ + #ifndef LIBH5_SERIALIZATION_HPP #define LIBH5_SERIALIZATION_HPP #include "./file.hpp" -#include "./group.hpp" #include "./generic.hpp" +#include +#include + namespace h5 { + /** + * @brief Serialize an object to a byte buffer. + * + * @details It first writes the object to a buffered memory file and then returns the underlying byte buffer. + * + * @tparam T Type of the object. + * @param x Object to be serialized. + * @return Byte buffer containing the serialized object. + */ template - std::vector serialize(T const &x) { + [[nodiscard]] std::vector serialize(T const &x) { file f{}; h5_write(f, "object", x); return f.as_buffer(); } - // ----------------------------- - + /** + * @brief Deserialize an object from a byte buffer. + * + * @details It first creates a buffered memory file from the given byte buffer and then reads the object from the file. + * + * @tparam T Type of the object. + * @param buf Byte buffer containing the serialized object. + * @return Deserialized object. + */ template - T deserialize(std::vector const &buf) { + [[nodiscard]] T deserialize(std::vector const &buf) { file f{buf}; return h5_read(f, "object"); } + } // namespace h5 #endif // LIBH5_SERIALIZATION_HPP diff --git a/c++/h5/stl/array.hpp b/c++/h5/stl/array.hpp index 42028fec..148610a3 100644 --- a/c++/h5/stl/array.hpp +++ b/c++/h5/stl/array.hpp @@ -14,45 +14,51 @@ // // Authors: Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::array objects from/to HDF5. + */ + #ifndef LIBH5_STL_ARRAY_HPP #define LIBH5_STL_ARRAY_HPP #include "../array_interface.hpp" +#include "../macros.hpp" -#include #include -#include +#include #include +#include +#include namespace h5 { /** - * Writes std::array into an hdf5 file + * @brief Write a std::array to an HDF5 dataset/subgroup. * - * @tparam T - * @param g HDF5 group - * @param name Name of the object in the HDF5 file - * @param a Array to save in the file + * @tparam T Value type of the std::array. + * @tparam N Size of the std::array. + * @param g h5::group in which the dataset/subgroup is created. + * @param name Name of the dataset/subgroup to which the std::array is written. + * @param a std::array to be written. */ template void h5_write(group g, std::string const &name, std::array const &a) { - if constexpr (std::is_same_v) { + // array of strings auto char_arr = std::array{}; std::transform(cbegin(a), cend(a), begin(char_arr), [](std::string const &s) { return s.c_str(); }); h5_write(g, name, char_arr); - } else if constexpr (std::is_arithmetic_v or is_complex_v or std::is_same_v or std::is_same_v or std::is_same_v) { - + // array of arithmetic/complex types or char* or const char* h5::array_interface::h5_array_view v{hdf5_type(), (void *)a.data(), 1, is_complex_v}; v.slab.count[0] = N; v.slab.stride[0] = 1; v.L_tot[0] = N; - h5::array_interface::write(g, name, v, true); - - } else { // generic unknown type to hdf5 + } else { + // array of generic type auto g2 = g.create_group(name); h5_write(g2, "shape", std::array{N}); for (int i = 0; i < N; ++i) h5_write(g2, std::to_string(i), a[i]); @@ -60,43 +66,40 @@ namespace h5 { } /** - * Reads std::array from HDF5 - * - * Use implementation h5_read from the array_interface + * @brief Read a std::array from an HDF5 dataset/subgroup. * - * @tparam T - * @param g HDF5 group - * @param name Name of the object in the HDF5 file - * @param a Array to read into + * @tparam T Value type of the std::array. + * @tparam N Size of the std::array. + * @param g h5::group containing the dataset/subgroup. + * @param name Name of the dataset/subgroup from which the std::array is read. + * @param a std::array to read into. */ template void h5_read(group g, std::string name, std::array &a) { - if constexpr (std::is_same_v) { + // array of strings auto char_arr = std::array{}; h5_read(g, name, char_arr); std::copy(cbegin(char_arr), cend(char_arr), begin(a)); - std::for_each(begin(char_arr), end(char_arr), [](char *cb) { free(cb); }); - + std::for_each(begin(char_arr), end(char_arr), [](char *cb) { free(cb); }); // NOLINT (we have to free the memory allocated by h5_read) } else if constexpr (std::is_arithmetic_v or is_complex_v or std::is_same_v or std::is_same_v or std::is_same_v) { - + // array of arithmetic/complex types or char* or const char* auto lt = array_interface::get_h5_lengths_type(g, name); - H5_EXPECTS(lt.rank() == 1 + lt.has_complex_attribute); H5_EXPECTS(N == lt.lengths[0]); if constexpr (is_complex_v) { - // Allow reading compound hdf5 dataype into array + // read complex values stored as a compound HDF5 datatype if (hdf5_type_equal(lt.ty, hdf5_type())) { - h5_read(g, name, reinterpret_cast &>(a)); + h5_read(g, name, reinterpret_cast &>(a)); // NOLINT (reinterpret_cast is safe here) return; } - // Allow to read non-complex data into array + // read non-complex data into std::array if (!lt.has_complex_attribute) { - std::cerr << "WARNING: Mismatching types in h5_read. Expecting a complex " + get_name_of_h5_type(hdf5_type()) - + " while the array stored in the hdf5 file has type " + get_name_of_h5_type(lt.ty) + "\n"; + std::cerr << "WARNING: HDF5 type mismatch while reading into a std::array: " + get_name_of_h5_type(hdf5_type()) + " =! " + + get_name_of_h5_type(lt.ty) + "\n"; std::array tmp{}; h5_read(g, name, tmp); std::copy(begin(tmp), end(tmp), begin(a)); @@ -104,22 +107,22 @@ namespace h5 { } } - array_interface::h5_array_view v{hdf5_type(), (void *)(a.data()), 1 /*rank*/, is_complex_v}; + // use array_interface to read + array_interface::h5_array_view v{hdf5_type(), (void *)(a.data()), 1, is_complex_v}; v.slab.count[0] = N; v.slab.stride[0] = 1; v.L_tot[0] = N; - array_interface::read(g, name, v, lt); - - } else { // generic unknown type to hdf5 + } else { + // array of generic type auto g2 = g.open_group(name); - // Check that shapes are compatible + // check that shapes are compatible auto h5_shape = std::array{}; h5_read(g2, "shape", h5_shape); H5_EXPECTS(N == h5_shape[0]); - // Read using appropriate h5_read implementation + // read using specialized h5_read implementation for (int i = 0; i < N; ++i) h5_read(g2, std::to_string(i), a[i]); } } diff --git a/c++/h5/stl/map.hpp b/c++/h5/stl/map.hpp index bbb94a97..9c68dbaf 100644 --- a/c++/h5/stl/map.hpp +++ b/c++/h5/stl/map.hpp @@ -14,60 +14,91 @@ // // Authors: Olivier Parcollet, Nils Wentzell, chuffa +/** + * @file + * @brief Provides functions to read/write std::map objects from/to HDF5. + */ + #ifndef LIBH5_STL_MAP_HPP #define LIBH5_STL_MAP_HPP -#include -#include +#include "../format.hpp" #include "../group.hpp" #include "./string.hpp" +#include +#include +#include +#include + namespace h5 { + /// Specialization of h5::hdf5_format_impl for std::map. template struct hdf5_format_impl> { static std::string invoke() { return "Dict"; } }; /** - * Map of type keyT for the key and valueT for the value. keyT can be any - * class as long as it is writeable to h5 (an operator "<" is needed to - * be used in a map in the first place). - */ + * @brief Write a std::map to an HDF5 subgroup. + * + * @tparam keyT Key type of the std::map. + * @tparam valueT Value type of the std::map. + * @param g h5::group in which the subgroup is created. + * @param name Name of the subgroup to which the std::map is written. + * @param m std::map to be written. + */ template - void h5_write(group f, std::string const &name, std::map const &M) { - auto gr = f.create_group(name); - write_hdf5_format(gr, M); + void h5_write(group g, std::string const &name, std::map const &m) { + // create the subgroup and write the hdf5_format tag + auto gr = g.create_group(name); + write_hdf5_format(gr, m); + // write element by element if constexpr (std::is_same_v) { - for (auto const &[key, val] : M) h5_write(gr, key, val); + // if key is a string, use it for the dataset name + for (auto const &[key, val] : m) h5_write(gr, key, val); } else { - int indx = 0; - for (auto const &[key, val] : M) { - auto element_gr = gr.create_group(std::to_string(indx)); + // otherwise, create a subgroup for each key-value pair + int idx = 0; + for (auto const &[key, val] : m) { + auto element_gr = gr.create_group(std::to_string(idx)); h5_write(element_gr, "key", key); h5_write(element_gr, "val", val); - ++indx; + ++idx; } } } + /** + * @brief Read a std::map from an HDF5 subgroup. + * + * @tparam keyT Key type of the std::map. + * @tparam valueT Value type of the std::map. + * @param g h5::group containing the subgroup. + * @param name Name of the subgroup from which the std::map is read. + * @param m std::map to read into. + */ template - void h5_read(group f, std::string const &name, std::map &M) { - auto gr = f.open_group(name); - M.clear(); + void h5_read(group g, std::string const &name, std::map &m) { + // open the subgroup and clear the map + auto gr = g.open_group(name); + m.clear(); + // loop over all subgroups and datasets in the current group for (auto const &x : gr.get_all_subgroup_dataset_names()) { valueT val; if constexpr (std::is_same_v) { + // if key is a string, read from the dataset with the same name h5_read(gr, x, val); - M.emplace(x, std::move(val)); + m.emplace(x, std::move(val)); } else { + // otherwise, read from the subgroup auto element_gr = gr.open_group(x); keyT key; h5_read(element_gr, "key", key); h5_read(element_gr, "val", val); - M.emplace(std::move(key), std::move(val)); + m.emplace(std::move(key), std::move(val)); } } } diff --git a/c++/h5/stl/optional.hpp b/c++/h5/stl/optional.hpp index d2ceaddc..9b7c42c9 100644 --- a/c++/h5/stl/optional.hpp +++ b/c++/h5/stl/optional.hpp @@ -14,36 +14,60 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::optional objects from/to HDF5. + */ + #ifndef LIBH5_STL_OPTIONAL_HPP #define LIBH5_STL_OPTIONAL_HPP -#include +#include "../format.hpp" #include "../group.hpp" #include "./string.hpp" +#include +#include + namespace h5 { + /// Specialization of h5::hdf5_format_impl for std::optional. template struct hdf5_format_impl> { static std::string invoke() { return hdf5_format_impl::invoke(); } }; /** - * Optional : write if the value is set. + * @brief Write a std::optional to an HDF5 dataset/subgroup (if it is set). + * + * @details Calls the specialized `h5_write` function for the value type of the std::optional. + * + * @tparam T Value type of std::optional. + * @param g h5::group in which the dataset/subgroup is created. + * @param name Name of the dataset/subgroup to which the std::optional value is written. + * @param opt std::optional to be written. */ template - void h5_write(group gr, std::string const &name, std::optional const &v) { - if (bool(v)) h5_write(gr, name, *v); + void h5_write(group g, std::string const &name, std::optional const &opt) { + if (opt) h5_write(g, name, *opt); } /** - * Read optional from the h5 + * @brief Read a std::optional from an HDF5 dataset/subgroup. + * + * @details Calls the specialized `h5_read` function for the value type of the std::optional. + * + * @tparam T Value type of std::optional. + * @param g h5::group containing the dataset/subgroup. + * @param name Name of the dataset/subgroup from which the std::optional value is read. + * @param opt std::optional to read into. */ template - void h5_read(group gr, std::string name, std::optional &v) { - v.reset(); - if (gr.has_key(name)) v.emplace(h5_read(gr, name)); + void h5_read(group g, std::string name, std::optional &opt) { + opt.reset(); + if (g.has_key(name)) opt.emplace(h5_read(g, name)); } + } // namespace h5 #endif // LIBH5_STL_OPTIONAL_HPP diff --git a/c++/h5/stl/pair.hpp b/c++/h5/stl/pair.hpp index 5d6e68e3..3ec0a4cc 100644 --- a/c++/h5/stl/pair.hpp +++ b/c++/h5/stl/pair.hpp @@ -14,42 +14,68 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::pair objects from/to HDF5. + */ + #ifndef LIBH5_STL_PAIR_HPP #define LIBH5_STL_PAIR_HPP -#include +#include "../format.hpp" #include "../group.hpp" #include "./string.hpp" +#include +#include + namespace h5 { + /// Specialization of h5::hdf5_format_impl for std::pair. template struct hdf5_format_impl> { static std::string invoke() { return "PythonTupleWrap"; } }; /** - * Write Pair of T1 and T2 as a subgroup with numbers + * @brief Write a std::pair to an HDF5 subgroup. + * + * @details Calls the specialized `h5_write` function for both values of the std::pair. + * + * @tparam T1 Value type #1. + * @tparam T1 Value type #2. + * @param g h5::group in which the subgroup is created. + * @param name Name of the subgroup to which the std::pair is written. + * @param p std::pair to be written. */ template - void h5_write(group f, std::string const &name, std::pair const &p) { - auto gr = f.create_group(name); + void h5_write(group g, std::string const &name, std::pair const &p) { + auto gr = g.create_group(name); write_hdf5_format(gr, p); h5_write(gr, "0", p.first); h5_write(gr, "1", p.second); } /** - * Read Pair of T1 and T2 from group + * @brief Read a std::pair from an HDF5 subgroup. + * + * @details Calls the specialized `h5_read` function for both values of the std::pair. + * + * @tparam T1 Value type #1. + * @tparam T1 Value type #2. + * @param g h5::group containing the subgroup. + * @param name Name of the subgroup from which the std::pair is read. + * @param p std::pair to read into. */ template - void h5_read(group f, std::string const &name, std::pair &p) { - auto gr = f.open_group(name); + void h5_read(group g, std::string const &name, std::pair &p) { + auto gr = g.open_group(name); if (gr.get_all_subgroup_dataset_names().size() != 2) - throw std::runtime_error("ERROR in std::pair h5_read: Incompatible number of group elements"); + throw std::runtime_error("Error in h5::h5_read: Reading a std::pair from a group with more/less than 2 subgroups/datasets is not allowed"); h5_read(gr, "0", p.first); h5_read(gr, "1", p.second); } + } // namespace h5 #endif // LIBH5_STL_PAIR_HPP diff --git a/c++/h5/stl/string.cpp b/c++/h5/stl/string.cpp index 4a928bbe..9d59a39e 100644 --- a/c++/h5/stl/string.cpp +++ b/c++/h5/stl/string.cpp @@ -14,252 +14,268 @@ // // Authors: Olivier Parcollet, Nils Wentzell +#include "../macros.hpp" #include "./string.hpp" + #include #include -#include -#include + #include +#include +#include +#include +#include +#include +#include +#include +#include namespace h5 { - // Returns a str datatype of a particular size (default=variable) - static datatype str_dtype(size_t size = H5T_VARIABLE) { - datatype dt = H5Tcopy(H5T_C_S1); - auto err = H5Tset_size(dt, size); - H5Tset_cset(dt, H5T_CSET_UTF8); // Always use UTF8 encoding - if (err < 0) throw std::runtime_error("Internal error in H5Tset_size"); - return dt; - } + namespace { - // ------------------------------------------------------------------ + // Returns an HDF5 datatype for a fixed-sized string with the given size or a variable-sized + // string if size == H5T_VARIABLE. + datatype str_dtype(size_t size = H5T_VARIABLE) { + datatype dt = H5Tcopy(H5T_C_S1); + auto err = H5Tset_size(dt, size); + H5Tset_cset(dt, H5T_CSET_UTF8); + if (err < 0) throw std::runtime_error("Error in str_dtype: H5Tset_size call failed"); + return dt; + } - void h5_write(group g, std::string const &name, std::string const &s) { + } // namespace + void h5_write(group g, std::string const &name, std::string const &s) { + // create the dataset for a variable-sized string datatype dt = str_dtype(); dataspace space = H5Screate(H5S_SCALAR); dataset ds = g.create_dataset(name, dt, space); + // write the string to dataset auto *s_ptr = s.c_str(); auto err = H5Dwrite(ds, dt, H5S_ALL, H5S_ALL, H5P_DEFAULT, &s_ptr); - if (err < 0) throw std::runtime_error("Error writing the string named" + name + " in the group" + g.name()); + if (err < 0) throw std::runtime_error("Error in h5_write: Writing a string to the dataset " + name + " in the group " + g.name() + " failed"); } - // -------------------- Read ---------------------------------------------- - void h5_read(group g, std::string const &name, std::string &s) { + // clear the string s = ""; + // open the dataset and get dataspace and datatype information dataset ds = g.open_dataset(name); dataspace dspace = H5Dget_space(ds); int rank = H5Sget_simple_extent_ndims(dspace); - if (rank != 0) throw std::runtime_error("Reading a string and got rank !=0"); + if (rank != 0) throw std::runtime_error("Error in h5_read: Reading a string from a dataspace with rank != 0 is not possible"); datatype dt = H5Dget_type(ds); H5_ASSERT(H5Tget_class(dt) == H5T_STRING); + // variable-sized string if (H5Tis_variable_str(dt)) { - char *rd_ptr[1]; - auto err = H5Dread(ds, dt, H5S_ALL, H5S_ALL, H5P_DEFAULT, rd_ptr); - if (err < 0) throw std::runtime_error("Error reading the string named" + name + " in the group" + g.name()); - s.append(*rd_ptr); - - // Free the resources allocated in the variable length read - err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr); - if (err < 0) throw std::runtime_error("Error in freeing resources in h5_read of variable-length string type"); - } else { + // first read into a char* pointer, then copy into the string + std::array rd_ptr{nullptr}; + auto err = H5Dread(ds, dt, H5S_ALL, H5S_ALL, H5P_DEFAULT, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read: Reading a string from the dataset " + name + " in the group " + g.name() + " failed"); + s.append(rd_ptr[0]); + + // free the resources allocated in the variable-length read + err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read: Freeing resources after reading a variable-length string failed"); + } else { // fixed-sized string std::vector buf(H5Tget_size(dt) + 1, 0x00); auto err = H5Dread(ds, dt, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buf[0]); - if (err < 0) throw std::runtime_error("Error reading the string named" + name + " in the group" + g.name()); + if (err < 0) throw std::runtime_error("Error in h5_read: Reading a string from the dataset " + name + " in the group " + g.name() + " failed"); s.append(&buf.front()); } } - // ------------------------------------------------------------------ - void h5_write_attribute(object obj, std::string const &name, std::string const &s) { - + // create the variable-sized string datatype and the dataspace datatype dt = str_dtype(); dataspace space = H5Screate(H5S_SCALAR); + // create the attribute attribute attr = H5Acreate2(obj, name.c_str(), dt, space, H5P_DEFAULT, H5P_DEFAULT); - if (!attr.is_valid()) throw std::runtime_error("Cannot create the attribute " + name); + if (!attr.is_valid()) throw std::runtime_error("Error in h5_write_attribute: Creating the attribute " + name + " failed"); + // write the string to attribute auto *s_ptr = s.c_str(); herr_t err = H5Awrite(attr, dt, &s_ptr); - if (err < 0) throw std::runtime_error("Cannot write the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5_write_attribute: Writing a string to the attribute " + name + " failed"); } - // -------------------- Read ---------------------------------------------- - - /// Return the attribute name of obj, and "" if the attribute does not exist. void h5_read_attribute(object obj, std::string const &name, std::string &s) { + // clear the string and return if the attribute is not present s = ""; - - // if the attribute is not present, return "" if (H5LTfind_attribute(obj, name.c_str()) == 0) return; + // open the attribute and get dataspace and datatype information attribute attr = H5Aopen(obj, name.c_str(), H5P_DEFAULT); dataspace dspace = H5Aget_space(attr); int rank = H5Sget_simple_extent_ndims(dspace); - if (rank != 0) throw std::runtime_error("Reading a string attribute and got rank !=0"); + if (rank != 0) throw std::runtime_error("Error in h5_read_attribute: Reading a string from a dataspace with rank != 0 is not possible"); datatype dt = H5Aget_type(attr); H5_ASSERT(H5Tget_class(dt) == H5T_STRING); + // variable-sized string if (H5Tis_variable_str(dt)) { - char *rd_ptr[1]; - auto err = H5Aread(attr, dt, rd_ptr); - if (err < 0) throw std::runtime_error("Cannot read the attribute " + name); - s.append(*rd_ptr); - - // Free the resources allocated in the variable length read - err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr); - if (err < 0) throw std::runtime_error("Error in freeing resources in h5_read of variable-length string type"); - } else { + // first read into a char* pointer, then copy into the string + std::array rd_ptr{nullptr}; + auto err = H5Aread(attr, dt, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute: Reading a string from the attribute " + name + " failed"); + s.append(rd_ptr[0]); + + // free the resources allocated in the variable-length read + err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute: Freeing resources after reading a variable-length string failed"); + } else { // fixed-sized string std::vector buf(H5Tget_size(dt) + 1, 0x00); auto err = H5Aread(attr, dt, (void *)(&buf[0])); - if (err < 0) throw std::runtime_error("Cannot read the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute: Reading a string from the attribute " + name + " failed"); s.append(&buf.front()); } } - // ------------------------------------------------------------------ - void h5_write_attribute_to_key(group g, std::string const &key, std::string const &name, std::string const &s) { - + // create the variable-sized string datatype and dataspace datatype dt = str_dtype(); dataspace dspace = H5Screate(H5S_SCALAR); + // create the attribute for a given key attribute attr = H5Acreate_by_name(g, key.c_str(), name.c_str(), dt, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - if (!attr.is_valid()) throw std::runtime_error("Cannot create the attribute " + name); + if (!attr.is_valid()) throw std::runtime_error("Error in h5_write_attribute_to_key: Creating the attribute " + name + " failed"); + // write the string to the attribute herr_t err = H5Awrite(attr, dt, (void *)(s.c_str())); - if (err < 0) throw std::runtime_error("Cannot write the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5_write_attribute_to_key: Writing a string to the attribute " + name + " failed"); } - // -------------------- Read ---------------------------------------------- - - /// Return the attribute name of key in group, and "" if the attribute does not exist. void h5_read_attribute_from_key(group g, std::string const &key, std::string const &name, std::string &s) { + // clear the string and return if the attribute is not present s = ""; - - // if the attribute is not present, return "" if (H5Aexists_by_name(g, key.c_str(), name.c_str(), H5P_DEFAULT) == 0) return; + // open the attribute and get dataspace and datatype information attribute attr = H5Aopen_by_name(g, key.c_str(), name.c_str(), H5P_DEFAULT, H5P_DEFAULT); dataspace dspace = H5Aget_space(attr); int rank = H5Sget_simple_extent_ndims(dspace); - if (rank != 0) throw std::runtime_error("Reading a string attribute and got rank !=0"); + if (rank != 0) throw std::runtime_error("Error in h5_read_attribute_to_key: Reading a string from a dataspace with rank != 0 is not possible"); datatype dt = H5Aget_type(attr); H5_ASSERT(H5Tget_class(dt) == H5T_STRING); + // variable-sized string if (H5Tis_variable_str(dt)) { - char *rd_ptr[1]; - auto err = H5Aread(attr, dt, rd_ptr); - if (err < 0) throw std::runtime_error("Cannot read the attribute " + name); - s.append(*rd_ptr); - - // Free the resources allocated in the variable length read - err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr); - if (err < 0) throw std::runtime_error("Error in freeing resources in h5_read of variable-length string type"); - } else { + // first read into a char* pointer, then copy into the string + std::array rd_ptr{nullptr}; + auto err = H5Aread(attr, dt, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute_to_key: Reading a string from the attribute " + name + " failed"); + s.append(rd_ptr[0]); + + // free the resources allocated in the variable-length read + err = H5Dvlen_reclaim(dt, dspace, H5P_DEFAULT, rd_ptr.data()); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute_to_key: Rreeing resources after reading a variable-length string failed"); + } else { // fixed-sized string std::vector buf(H5Tget_size(dt) + 1, 0x00); auto err = H5Aread(attr, dt, &buf[0]); - if (err < 0) throw std::runtime_error("Cannot read the attribute " + name); + if (err < 0) throw std::runtime_error("Error in h5_read_attribute_to_key: Reading a string from the attribute " + name + " failed"); s.append(&buf.front()); } } - // --------------------------- char_buf ------------------------------------- - + // HDF5 datatype of a char_buf is a fixed-sized string datatype char_buf::dtype() const { return str_dtype(lengths.back()); } - // the dataspace (without last dim, which is the string). + // dataspace is an n-dimensional array of fixed-sized strings, each of length max_length + 1 dataspace char_buf::dspace() const { - dataspace ds = H5Screate_simple(lengths.size() - 1, lengths.data(), nullptr); // rank is size of length - 1 - if (!ds.is_valid()) throw make_runtime_error("Cannot create the dataset"); + dataspace ds = H5Screate_simple(static_cast(lengths.size()) - 1, lengths.data(), nullptr); + if (!ds.is_valid()) throw make_runtime_error("Error in h5::char_buf: Creating the dataspace for the char_buf failed"); return ds; } - // ----------- WRITE ------------ - void h5_write(group g, std::string const &name, char_buf const &cb) { + // create the dataset for the char_buf auto dt = cb.dtype(); auto dspace = cb.dspace(); + dataset ds = g.create_dataset(name, dt, dspace); - dataset ds = g.create_dataset(name, dt, dspace); - + // write to the dataset auto err = H5Dwrite(ds, dt, dspace, H5S_ALL, H5P_DEFAULT, (void *)cb.buffer.data()); - if (err < 0) throw make_runtime_error("Error writing the vector ", name, " in the group", g.name()); + if (err < 0) throw make_runtime_error("Error in h5_write: Writing a char_buf to the dataset ", name, " in the group ", g.name(), " failed"); } - // ----------- READ ------------ - - void h5_read(group g, std::string const &name, char_buf &_cb) { - dataset ds = g.open_dataset(name); - dataspace d_space = H5Dget_space(ds); - datatype ty = H5Dget_type(ds); + void h5_read(group g, std::string const &name, char_buf &cb) { + // open the dataset and get dataspace and datatype information + dataset ds = g.open_dataset(name); + dataspace dspace = H5Dget_space(ds); + datatype ty = H5Dget_type(ds); + // prepare the char_buf to be read into char_buf cb_out; - - int dim = H5Sget_simple_extent_ndims(d_space); + // number of strings + int dim = H5Sget_simple_extent_ndims(dspace); cb_out.lengths.resize(dim); - H5Sget_simple_extent_dims(d_space, cb_out.lengths.data(), nullptr); - + H5Sget_simple_extent_dims(dspace, cb_out.lengths.data(), nullptr); + // max. length of the strings + 1 size_t size = H5Tget_size(ty); cb_out.lengths.push_back(size); - + // resize the buffer long ltot = std::accumulate(cb_out.lengths.begin(), cb_out.lengths.end(), 1, std::multiplies<>()); cb_out.buffer.resize(std::max(ltot, 1l), 0x00); + // read into the buffer H5_ASSERT(hdf5_type_equal(ty, cb_out.dtype())); auto err = H5Dread(ds, ty, cb_out.dspace(), H5S_ALL, H5P_DEFAULT, (void *)cb_out.buffer.data()); - if (err < 0) throw make_runtime_error("Error reading the vector ", name, " in the group", g.name()); + if (err < 0) throw make_runtime_error("Error in h5_read: Reading a char_buf from the dataset ", name, " in the group ", g.name(), " failed"); - _cb = std::move(cb_out); + // move to output char_buf + cb = std::move(cb_out); } - // ----------- WRITE ATTRIBUTE ------------ - void h5_write_attribute(object obj, std::string const &name, char_buf const &cb) { + // datatype and dataspace of char_buf auto dt = cb.dtype(); auto dspace = cb.dspace(); + // create the attribute attribute attr = H5Acreate2(obj, name.c_str(), dt, dspace, H5P_DEFAULT, H5P_DEFAULT); - if (!attr.is_valid()) throw make_runtime_error("Cannot create the attribute ", name); + if (!attr.is_valid()) throw make_runtime_error("Error in h5_write_attribute: Creating the attribute ", name, " failed"); + // write the char_buf to the attribute herr_t status = H5Awrite(attr, dt, (void *)cb.buffer.data()); - if (status < 0) throw make_runtime_error("Cannot write the attribute ", name); + if (status < 0) throw make_runtime_error("Error in h5_write_attribute: Writing a char_buf to the attribute ", name, " failed"); } - // ----- read attribute ----- - - void h5_read_attribute(object obj, std::string const &name, char_buf &_cb) { + void h5_read_attribute(object obj, std::string const &name, char_buf &cb) { + // open the attribute and get dataspace and datatype information attribute attr = H5Aopen(obj, name.c_str(), H5P_DEFAULT); - if (!attr.is_valid()) throw make_runtime_error("Cannot open the attribute ", name); + if (!attr.is_valid()) throw make_runtime_error("Error in h5_read_attribute: Opening the attribute ", name, " failed"); dataspace d_space = H5Aget_space(attr); datatype ty = H5Aget_type(attr); + // prepare the char_buf to be read into char_buf cb_out; - + // number of strings int dim = H5Sget_simple_extent_ndims(d_space); cb_out.lengths.resize(dim); H5Sget_simple_extent_dims(d_space, cb_out.lengths.data(), nullptr); - + // max. length of the strings + 1 size_t size = H5Tget_size(ty); cb_out.lengths.push_back(size); - + // resize the buffer long ltot = std::accumulate(cb_out.lengths.begin(), cb_out.lengths.end(), 1, std::multiplies<>()); cb_out.buffer.resize(std::max(ltot, 1l), 0x00); + // read into the buffer H5_ASSERT(hdf5_type_equal(ty, cb_out.dtype())); auto err = H5Aread(attr, ty, (void *)cb_out.buffer.data()); - if (err < 0) throw make_runtime_error("Cannot read the attribute ", name); + if (err < 0) throw make_runtime_error("Error in h5_read_attribute: Reading a char_buf from the attribute ", name, " failed"); - _cb = std::move(cb_out); + // move to output char_buf + cb = std::move(cb_out); } } // namespace h5 diff --git a/c++/h5/stl/string.hpp b/c++/h5/stl/string.hpp index fbb2b7dd..9ceca2bf 100644 --- a/c++/h5/stl/string.hpp +++ b/c++/h5/stl/string.hpp @@ -14,117 +14,193 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::string, char* and h5::char_buf objects from/to HDF5. + */ + #ifndef LIBH5_STL_STRING_HPP #define LIBH5_STL_STRING_HPP #include "../group.hpp" + #include +#include namespace h5 { + // Forward declaration. template struct hdf5_format_impl; + /// Specialization of h5::hdf5_format_impl for std::string. template <> struct hdf5_format_impl { static std::string invoke() { return "string"; } }; /** - * Write a string into an h5::group - * - * Format : Fixed size string + * @brief Write a std::string to an HDF5 dataset. * - * @param g The h5::group - * @param name The name of the dataset - * @param s String to be saved. + * @param g h5::group in which the dataset is created. + * @param name Name of the dataset. + * @param s std::string to be written. */ void h5_write(group g, std::string const &name, std::string const &s); - /// Write a const char array as a string + /** + * @brief Write a `const char*` to an HDF5 dataset. + * + * @param g h5::group in which the dataset is created. + * @param name Name of the dataset. + * @param s `const char*` to be written. + */ inline void h5_write(group g, std::string const &name, const char *s) { h5_write(g, name, std::string{s}); } /** - * Read a string from an h5::group + * @brief Read a string from an HDF5 dataset into a std::string. * - * @param g The h5::group - * @param name The name of the dataset - * @param s The string to read into + * @param g h5::group containing the dataset. + * @param name Name of the dataset. + * @param s std::string to read into. */ void h5_read(group g, std::string const &name, std::string &s); - // Explicitly forbidden. + /** + * @brief Read a string from an HDF5 dataset into a `char*`. + * @warning Reading into a `char*` is not allowed. Use h5::h5_read(group, std::string const &, std::string &) instead. + */ inline void h5_read(group g, std::string const &name, char *s) = delete; /** - * Write a string attribute to an object + * @brief Write a std::string to an HDF5 attribute. * - * @param obj The object to write the attribute to - * @param name The name of the attribute - * @param s The string attribute - */ + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param s std::string to be written. + */ void h5_write_attribute(object obj, std::string const &name, std::string const &s); - /// Write a const char array as a string attribute of an h5::object + /** + * @brief Write a `const char*` to an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param s 'const char*' to be written. + */ inline void h5_write_attribute(object obj, std::string const &name, const char *s) { h5_write_attribute(obj, name, std::string{s}); } /** - * Read a string attribute from an object + * @brief Read a string from an HDF5 attribute into a std::string. + * + * @details If the attribute does not exist, an empty string is returned. * - * @param obj The object to read the attribute from - * @param name The name of the attribute - * @param value The string to read into + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param s std::string to read into. */ void h5_read_attribute(object obj, std::string const &name, std::string &s); - // Explicitly forbidden + /** + * @brief Read a string from an HDF5 attribute into a `char*`. + * @warning Reading into a `char*` is not allowed. Use h5::h5_read_attribute(object, std::string const &, std::string &) instead. + */ inline void h5_read_attribute(object obj, std::string const &name, char *s) = delete; /** - * Write a string attribute to a key in an h5::group + * @brief Write a std::string to an HDF5 attribute. * - * @param g The h5::group - * @param key The key - * @param name The name of the attribute - * @param s The string attribute - */ + * @param g h5::group containing the HDF5 object to which the attribute is attached. + * @param key Name of the object. + * @param name Name of the attribute. + * @param s std::string to be written. + */ void h5_write_attribute_to_key(group g, std::string const &key, std::string const &name, std::string const &s); - /// Write a const char array as a string attribute of a particular key + /** + * @brief Write a `const char*` to an HDF5 attribute. + * + * @param g h5::group containing the HDF5 object to which the attribute is attached. + * @param key Name of the object. + * @param name Name of the attribute. + * @param s `const char*` to be written. + */ inline void h5_write_attribute_to_key(group g, std::string const &key, std::string const &name, const char *s) { h5_write_attribute_to_key(g, key, name, std::string{s}); } /** - * Read a string attribute from a key in an h5::group + * @brief Read a string from an HDF5 attribute into a std::string. * - * @param g The h5::group - * @param key The key - * @param name The name of the attribute - * @param s The string to read into - */ + * @details If the attribute does not exist, an empty string is returned. + * + * @param g h5::group containing the HDF5 object to which the attribute is attached. + * @param key Name of the object. + * @param name Name of the attribute. + * @param s std::string to read into. + */ void h5_read_attribute_from_key(group g, std::string const &key, std::string const &name, std::string &s); - // --------------------- char_buf ----------------------- - - // char_buf contains an n dimensional array of strings as fixed size strings, flatten in a 1d array of char. - // the last dimension is the max length of the strings + 1, because of the ending 0 in C ! + /** + * @brief Stores an arbitrary number of strings in a 1-dimensional std::vector. + * + * @details Each string is assumed to have the same length. If a string is shorter than this length, it is padded + * with zeros. The `lengths` member should have the following entries: the number of strings in each dimension and + * the max. allowed length of the strings + 1. For example, if the original strings are stored in a 2-dimensional + * array of size `MxN` and the longest string is of length `L`, then `lengths = {M, N, L+1}`. + * + * The HDF5 datatype is a fixed-length string of size `lengths.back()` and the HDF5 dataspace is an n-dimensional + * array of fixed-sized strings. + */ struct char_buf { + /// Stores strings in a 1-dimensional vector. std::vector buffer; + + /// Stores the number of strings in each dimension and the max. allowed length of the strings + 1. v_t lengths; - // the string datatype + /// Get the HDF5 datatype. [[nodiscard]] datatype dtype() const; - // the dataspace (without last dim, which is the string). + /// Get the HDF5 dataspace. [[nodiscard]] dataspace dspace() const; }; - // read/write for char_buf + /** + * @brief Write an h5::char_buf to an HDF5 dataset. + * + * @param g h5::group in which the dataset is created. + * @param name Name of the dataset. + * @param cb h5::char_buf to be written. + */ void h5_write(group g, std::string const &name, char_buf const &cb); + + /** + * @brief Read an h5::char_buf from an HDF5 dataset. + * + * @param g h5::group containing the dataset. + * @param name Name of the dataset. + * @param cb h5::char_buf to read into. + */ + void h5_read(group g, std::string const &name, char_buf &cb); + + /** + * @brief Write an h5::char_buf to an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param cb h5::char_buf to be written. + */ void h5_write_attribute(object obj, std::string const &name, char_buf const &cb); - void h5_read(group g, std::string const &name, char_buf &_cb); - void h5_read_attribute(object obj, std::string const &name, char_buf &_cb); + + /** + * @brief Read an h5::char_buf from an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param cb h5::char_buf to read into. + */ + void h5_read_attribute(object obj, std::string const &name, char_buf &cb); } // namespace h5 diff --git a/c++/h5/stl/tuple.hpp b/c++/h5/stl/tuple.hpp index 6bade8c9..941d124d 100644 --- a/c++/h5/stl/tuple.hpp +++ b/c++/h5/stl/tuple.hpp @@ -14,53 +14,84 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::tuple object from/to HDF5. + */ + #ifndef LIBH5_STL_TUPLE_HPP #define LIBH5_STL_TUPLE_HPP -#include +#include "../format.hpp" #include "../group.hpp" #include "./string.hpp" +#include +#include +#include +#include +#include + namespace h5 { + /// Specialization of h5::hdf5_format_impl for std::tuple. template struct hdf5_format_impl> { static std::string invoke() { return "PythonTupleWrap"; } }; namespace detail { - template - void h5_write_tuple_impl(group gr, std::string const &, std::tuple const &tpl, std::index_sequence) { - (h5_write(gr, std::to_string(Is), std::get(tpl)), ...); + + // Helper function to write a tuple to HDF5. + template + void h5_write_tuple_impl(group g, std::string const &, std::tuple const &tup, std::index_sequence) { + (h5_write(g, std::to_string(Is), std::get(tup)), ...); } - template - void h5_read_tuple_impl(group gr, std::string const &, std::tuple &tpl, std::index_sequence) { - if (gr.get_all_subgroup_dataset_names().size() != sizeof...(Is)) - throw std::runtime_error("ERROR in std::tuple h5_read: Tuple size incompatible to number of group elements"); - (h5_read(gr, std::to_string(Is), std::get(tpl)), ...); + // Helper function to read a tuple from HDF5. + template + void h5_read_tuple_impl(group g, std::string const &, std::tuple &tup, std::index_sequence) { + if (g.get_all_subgroup_dataset_names().size() != sizeof...(Is)) + throw std::runtime_error( + "Error in h5_read_tuple_impl: Reading a std::tuple from a group with more/less than sizeof...(Ts) subgroups/datasets is not allowed"); + (h5_read(g, std::to_string(Is), std::get(tup)), ...); } } // namespace detail /** - * Tuple of T... as a subgroup with numbers + * @brief Write a std::tuple to an HDF5 subgroup. + * + * @details Calls the specialized `h5_write` function for every element of the std::tuple. + * + * @tparam Ts Tuple types. + * @param g h5::group in which the subgroup is created. + * @param name Name of the subgroup to which the std::tuple is written. + * @param tup std::tuple to be written. */ - template - void h5_write(group f, std::string const &name, std::tuple const &tpl) { - auto gr = f.create_group(name); - write_hdf5_format(gr, tpl); - detail::h5_write_tuple_impl(gr, name, tpl, std::index_sequence_for{}); + template + void h5_write(group g, std::string const &name, std::tuple const &tup) { + auto gr = g.create_group(name); + write_hdf5_format(gr, tup); + detail::h5_write_tuple_impl(gr, name, tup, std::index_sequence_for{}); } /** - * Tuple of T... + * @brief Read a std::tuple from an HDF5 subgroup. + * + * @details Calls the specialized `h5_read` function for every value of the std::tuple. + * + * @tparam Ts Tuple types. + * @param g h5::group containing the subgroup. + * @param name Name of the subgroup from which the std::tuple is read. + * @param tup std::tuple to read into. */ - template - void h5_read(group f, std::string const &name, std::tuple &tpl) { - auto gr = f.open_group(name); - detail::h5_read_tuple_impl(gr, name, tpl, std::index_sequence_for{}); + template + void h5_read(group g, std::string const &name, std::tuple &tup) { + auto gr = g.open_group(name); + detail::h5_read_tuple_impl(gr, name, tup, std::index_sequence_for{}); } + } // namespace h5 #endif // LIBH5_STL_TUPLE_HPP diff --git a/c++/h5/stl/variant.hpp b/c++/h5/stl/variant.hpp index 97169608..3715642b 100644 --- a/c++/h5/stl/variant.hpp +++ b/c++/h5/stl/variant.hpp @@ -14,51 +14,78 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::variant object from/to HDF5. + */ + #ifndef LIBH5_STL_VARIANT_HPP #define LIBH5_STL_VARIANT_HPP -#include - +#include "../format.hpp" +#include "../generic.hpp" #include "../group.hpp" #include "./string.hpp" -#include "../generic.hpp" + +#include +#include +#include namespace h5 { + /// Specialization of h5::hdf5_format_impl for std::variant. template struct hdf5_format_impl> { static std::string invoke() = delete; }; /** + * @brief Write a std::variant to an HDF5 dataset/subgroup. + * + * @details Calls the specialized `h5_write` for the type currently stored in the std::variant. + * + * @tparam Ts Variant types. + * @param g h5::group in which the dataset/subgroup is created. + * @param name Name of the dataset/subgroup to which the std::variant is written. + * @param v std::variant to be written. */ - template - void h5_write(group gr, std::string const &name, std::variant const &v) { - visit([&](auto const &x) { h5_write(gr, name, x); }, v); + template + void h5_write(group g, std::string const &name, std::variant const &v) { + std::visit([&](auto const &x) { h5_write(g, name, x); }, v); } - template - void h5_read_variant_helper(VT &v, datatype dt, group gr, std::string const &name) { + // Helper function to read a std::variant from HDF5. + template + void h5_read_variant_helper(VT &v, datatype dt, group g, std::string const &name) { + // finds the correct h5_read recursively if (hdf5_type_equal(hdf5_type(), dt)) { - v = VT{h5_read(gr, name)}; + v = VT{h5_read(g, name)}; return; } - if constexpr (sizeof...(T) > 0) - h5_read_variant_helper(v, dt, gr, name); + if constexpr (sizeof...(Ts) > 0) + h5_read_variant_helper(v, dt, g, name); else - throw std::runtime_error("Error in h5_read: std::variant<...> not compatible with Format \n"); + throw std::runtime_error("Error in h5_read_variant_helper: Type stored in the variant has no corresponding HDF5 datatype"); } /** - * Read variant from the h5 + * @brief Read a std::variant from an HDF5 dataset. + * + * @warning This function only works, if name points to a dataset and not a group. Depending on the HDF5 datatype + * of the dataset, it calls the specialized `h5_read`. + * + * @tparam Ts Variant types. + * @param g h5::group containing the dataset. + * @param name Name of the dataset from which the std::variant is read. + * @param v std::variant to read into. */ - template - void h5_read(group gr, std::string const &name, std::variant &v) { + template + void h5_read(group g, std::string const &name, std::variant &v) { // name is a group --> triqs object // assume for the moment, name is a dataset. - dataset ds = gr.open_dataset(name); + dataset ds = g.open_dataset(name); datatype dt = get_hdf5_type(ds); - h5_read_variant_helper, T...>(v, dt, gr, name); + h5_read_variant_helper, Ts...>(v, dt, g, name); } } // namespace h5 diff --git a/c++/h5/stl/vector.cpp b/c++/h5/stl/vector.cpp index ed6deb3e..552c132a 100644 --- a/c++/h5/stl/vector.cpp +++ b/c++/h5/stl/vector.cpp @@ -15,26 +15,22 @@ // Authors: Philipp Dumitrescu, Olivier Parcollet, Nils Wentzell #include "./vector.hpp" + #include #include -#include + +#include #include #include -#include "./string.hpp" - namespace h5 { - //------------------ to_char_buf ------------------------------ - - // copy to the buffer, with each string having the same length char_buf to_char_buf(std::vector const &v) { - + // get size of longest string size_t s = 1; for (auto &x : v) s = std::max(s, x.size() + 1); - auto len = v_t{v.size(), s}; - // copy to the buffer + // copy each string to a buffer and pad with zeros std::vector buf; buf.resize(std::max(v.size() * s, 1ul), 0x00); size_t i = 0; @@ -43,20 +39,20 @@ namespace h5 { ++i; } + // return char_buf + auto len = v_t{v.size(), s}; return {buf, len}; } - // copy to the buffer, with each string having the same length char_buf to_char_buf(std::vector> const &v) { - + // get size of longest vector and string size_t s = 1, lv = 0; for (auto &v1 : v) { lv = std::max(lv, v1.size()); for (auto &x : v1) s = std::max(s, x.size() + 1); } - auto len = v_t{v.size(), lv, s}; - // copy to the buffer + // copy each string to a buffer and pad with zeros std::vector buf; buf.resize(std::max(v.size() * lv * s, 1ul), 0x00); for (int i = 0, k = 0; i < v.size(); i++) @@ -64,19 +60,21 @@ namespace h5 { if (j < v[i].size()) strcpy(&buf[k * s], v[i][j].c_str()); } + // return char_buf + auto len = v_t{v.size(), lv, s}; return {buf, len}; } - //------------------- from_char_buf----------------------------- - void from_char_buf(char_buf const &cb, std::vector &v) { + // prepare vector v.clear(); v.resize(cb.lengths[0]); - auto len_string = cb.lengths[1]; - long i = 0; + // loop over all strings + auto len_string = cb.lengths[1]; + long i = 0; for (auto &x : v) { - // Use full range from char_buf and remove null characters + // use full range from char_buf and remove null characters const char *bptr = &cb.buffer[i * len_string]; x = std::string(bptr, bptr + len_string); x.erase(std::remove(begin(x), end(x), '\0'), end(x)); @@ -84,18 +82,18 @@ namespace h5 { } } - //-------- - void from_char_buf(char_buf const &cb, std::vector> &v) { + // prepare vector v.clear(); v.resize(cb.lengths[0]); + + // loop over all vectors and all strings auto inner_vec_size = cb.lengths[1]; auto len_string = cb.lengths[2]; - - long i = 0; + long i = 0; for (auto &v_inner : v) { for (int j = 0; j < inner_vec_size; ++j, ++i) { - // Use full range from char_buf and remove null characters + // use full range from char_buf and remove null characters const char *bptr = &cb.buffer[i * len_string]; auto s = std::string(bptr, bptr + len_string); s.erase(std::remove(begin(s), end(s), '\0'), end(s)); @@ -104,16 +102,12 @@ namespace h5 { } } - // ----------- WRITE ATTRIBUTE ------------ - void h5_write_attribute(object obj, std::string const &name, std::vector const &v) { h5_write_attribute(obj, name, to_char_buf(v)); } void h5_write_attribute(object obj, std::string const &name, std::vector> const &v) { h5_write_attribute(obj, name, to_char_buf(v)); } - // ----------- READ ATTRIBUTE ------------ - void h5_read_attribute(object obj, std::string const &name, std::vector &v) { char_buf cb; h5_read_attribute(obj, name, cb); diff --git a/c++/h5/stl/vector.hpp b/c++/h5/stl/vector.hpp index 1f1a2653..6dd10446 100644 --- a/c++/h5/stl/vector.hpp +++ b/c++/h5/stl/vector.hpp @@ -14,20 +14,34 @@ // // Authors: Olivier Parcollet, Nils Wentzell +/** + * @file + * @brief Provides functions to read/write std::vector objects from/to HDF5. + */ + #ifndef LIBH5_STL_VECTOR_HPP #define LIBH5_STL_VECTOR_HPP -#include -#include -#include "../group.hpp" -#include "./string.hpp" #include "../format.hpp" +#include "../group.hpp" #include "../scalar.hpp" +#include "./string.hpp" + +#include +#include +#include namespace h5 { namespace array_interface { + /** + * @brief Create an h5::array_interface::h5_array_view for a std::vector. + * + * @tparam T Value type of std::vector. + * @param v std::vector. + * @return h5::array_interface::h5_array_view of rank 1. + */ template h5_array_view h5_array_view_from_vector(std::vector const &v) { h5_array_view res{hdf5_type(), (void *)v.data(), 1, is_complex_v>}; @@ -38,109 +52,159 @@ namespace h5 { } // namespace array_interface - // ---------------------------------------------------------------------------- // FIXME : CLEAN THIS - // Special case of vector < string > - + // Specialization of h5::hdf5_format_impl for std::vector. H5_SPECIALIZE_FORMAT2(std::vector, vector); + /// Specialization of h5::hdf5_format_impl for std::vector. template struct hdf5_format_impl> { static std::string invoke() { return "List"; } }; - // ---------------------------------------------------------------------------- - // details for string case + /** + * @brief Create an h5::char_buf from a vector of strings. + * + * @param v Vector of strings. + * @return h5::char_buf containing the strings. + */ char_buf to_char_buf(std::vector const &v); + + /** + * @brief Create an h5::char_buf from a vector of vectors of strings. + * + * @param v Vector of a vector of strings. + * @return h5::char_buf containing the strings. + */ char_buf to_char_buf(std::vector> const &v); + /** + * @brief Create a vector of strings from an h5::char_buf. + * + * @param cb h5::char_buf. + * @param v Vector of strings. + */ void from_char_buf(char_buf const &cb, std::vector &v); - void from_char_buf(char_buf const &cb, std::vector> &v); - // ---------------------------------------------------------------------------- + /** + * @brief Create a vector of vectors of strings from an h5::char_buf. + * + * @param cb h5::char_buf. + * @param v Vector of vectors of strings. + */ + void from_char_buf(char_buf const &cb, std::vector> &v); /** - * Writes std::vector into HDF5 + * @brief Write a std::vector to an HDF5 dataset/subgroup. * - * Format - * * If T is a simple type (int, double, complex), it is a 1d array. - * * If T is std::string, it is a 2d array of char of dimensions (length of vector, max length of strings) - * * Otherwise, it opens a subgroup and writes each element as 0,1,2,3 ... in the subgroup + * @details Depending on the type of `T`, the following is written: + * - If `T` is a simple type (arithmetic or complex), a 1d dataset is written. + * - If `T` is `std::string`, an h5::char_buf is written, i.e. a 2d dataset of char with dimensions + * (length of vector, max length of strings). + * - Otherwise, it creates a subgroup and writes each element to the subgroup. * - * @tparam T - * @param g HDF5 group - * @param name Name of the object in the HDF5 file - * @param v Vector to save in the file + * @tparam T Value tupe of std::vector. + * @param g h5::group in which the dataset/subgroup is created. + * @param name Name of the dataset/subgroup to which the std::vector is written. + * @param v std::vector to be written. */ template void h5_write(group g, std::string const &name, std::vector const &v) { - if constexpr (std::is_arithmetic_v or is_complex_v) { - + // vector of arithmetic/complex types array_interface::write(g, name, array_interface::h5_array_view_from_vector(v), true); - } else if constexpr (std::is_same_v or std::is_same_v>) { - + // vector (of vectors) of strings h5_write(g, name, to_char_buf(v)); - - } else { // generic type - + } else { + // vector of generic types auto gr = g.create_group(name); write_hdf5_format(gr, v); for (int i = 0; i < v.size(); ++i) h5_write(gr, std::to_string(i), v[i]); } } - // ---------------------------------------------------------------------------- - /** - * Reads std::vector from HDF5 + * @brief Read a std::vector from an HDF5 dataset/subgroup. * - * Format - * * If T is a simple type (int, double, complex), it is a 1d array. - * * If T is std::string, it is a 2d array of char of dimensions (length of vector, max length of strings) - * * Otherwise, it opens a subgroup and writes each element as 0,1,2,3 ... in the subgroup + * @details Depending on the type of `T`, the following is read: + * - If `T` is a simple type (arithmetic or complex), a 1d dataset is read. + * - If `T` is `std::string`, an h5::char_buf is read, i.e. a 2d dataset of char with dimensions + * (length of vector, max length of strings). + * - Otherwise, it opens a subgroup and reads each element from the subgroup. * - * @tparam T - * @param g HDF5 group - * @param name Name of the object in the HDF5 file - * @param v Vector to read into + * @tparam T Value tupe of std::vector. + * @param g h5::group containing the dataset/subgroup. + * @param name Name of the dataset/subgroup from which the std::vector is read. + * @param v std::vector to read into. */ template void h5_read(group g, std::string name, std::vector &v) { - if (not g.has_key(name)) throw make_runtime_error("h5 : group has no key ", name); - - if (g.has_subgroup(name)) { // stored as subgroup with keys of generic type + // throw exception if no link with the given name exists + if (not g.has_key(name)) throw make_runtime_error("Error in h5_read: Dataset/Subgroup with name ", name, " does not exist"); + if (g.has_subgroup(name)) { + // vector of generic type auto g2 = g.open_group(name); v.resize(g2.get_all_dataset_names().size() + g2.get_all_subgroup_names().size()); for (int i = 0; i < v.size(); ++i) { h5_read(g2, std::to_string(i), v[i]); } - } else { if constexpr (std::is_arithmetic_v or is_complex_v) { - + // vector of arithmetic/complex types auto lt = array_interface::get_h5_lengths_type(g, name); - if (lt.rank() != 1 + is_complex_v) throw make_runtime_error("h5 : reading a vector and I got an array of rank ", lt.rank()); + if (lt.rank() != 1 + is_complex_v) + throw make_runtime_error("Error in h5_read: Reading a vector from an array of rank ", lt.rank(), " is not allowed"); v.resize(lt.lengths[0]); array_interface::read(g, name, array_interface::h5_array_view_from_vector(v), lt); - } else if constexpr (std::is_same_v or std::is_same_v>) { - + // vector of strings or vector of vector of strings char_buf cb; h5_read(g, name, cb); from_char_buf(cb, v); - } else { - throw make_runtime_error("h5 : unrecognized dataset type in read of std::vector"); + // unsupported type + throw make_runtime_error("Error in h5_read: HDF5 datatype not supported for reading into a std::vector"); } } } - void h5_write_attribute(object obj, std::string const &name, std::vector> const &V); - void h5_read_attribute(object obj, std::string const &name, std::vector> &V); + /** + * @brief Write a vector of vectors of strings to an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v Vector of vectors of strings to be written. + */ + void h5_write_attribute(object obj, std::string const &name, std::vector> const &v); + + /** + * @brief Read a vector of vectors of strings from an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v Vector of vectors of strings to read into. + */ + void h5_read_attribute(object obj, std::string const &name, std::vector> &v); + + /** + * @brief Write a vectors of strings to an HDF5 attribute. + * + * @param obj h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v Vector of strings to be written. + */ + void h5_write_attribute(object obj, std::string const &name, std::vector const &v); + + /** + * @brief Read a vector of strings from an HDF5 attribute. + * + * @param obj Parent h5::object to which the attribute is attached. + * @param name Name of the attribute. + * @param v Vector of strings to read into. + */ + void h5_read_attribute(object obj, std::string const &name, std::vector &v); - void h5_write_attribute(object obj, std::string const &name, std::vector const &V); - void h5_read_attribute(object obj, std::string const &name, std::vector &V); + /** @} */ } // namespace h5