TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
h5.hpp
Go to the documentation of this file.
1// Copyright (c) 2019--present, The Simons Foundation
2// This file is part of TRIQS/nda and is licensed under the Apache License, Version 2.0.
3// SPDX-License-Identifier: Apache-2.0
4// See LICENSE in the root of this distribution for details.
5
10
11#pragma once
12
13#include "./concepts.hpp"
14#include "./declarations.hpp"
15#include "./exceptions.hpp"
16#include "./layout/for_each.hpp"
17#include "./layout/range.hpp"
18#include "./traits.hpp"
19
20#include <h5/h5.hpp>
21
22#include <algorithm>
23#include <array>
24#include <concepts>
25#include <cstddef>
26#include <string>
27#include <tuple>
28#include <type_traits>
29#include <utility>
30#include <vector>
31
32namespace nda {
33
38
39 namespace detail {
40
41 // Resize a given array to fit a given shape or check if a given view fits the shape.
42 template <MemoryArray A>
43 void resize_or_check(A &a, std::array<long, A::rank> const &shape) {
44 if constexpr (is_regular_v<A>) {
45 a.resize(shape);
46 } else {
47 if (a.shape() != shape) NDA_RUNTIME_ERROR << "Error in nda::detail::resize_or_check: Dimension mismatch: " << shape << " != " << a.shape();
48 }
49 }
50
51 // Given an array/view, prepare and return the corresponding h5::array_view to be written/read into.
52 template <MemoryArray A>
53 auto prepare_h5_array_view(const A &a) {
54 auto [parent_shape, h5_strides] =
55 h5::array_interface::get_parent_shape_and_h5_strides(a.indexmap().strides().data(), A::rank, a.shape().data());
56 auto v = h5::array_interface::array_view{h5::hdf5_type<get_value_t<A>>(), (void *)a.data(), A::rank, is_complex_v<typename A::value_type>};
57 for (int u = 0; u < A::rank; ++u) {
58 v.slab.count[u] = a.shape()[u];
59 v.slab.stride[u] = h5_strides[u];
60 v.parent_shape[u] = parent_shape[u];
61 }
62 return v;
63 }
64
65 // Create an h5::char_buf from an 1-dimensional nda::MemoryArray.
66 template <MemoryArrayOfRank<1> A>
67 h5::char_buf to_char_buf(A const &a) {
68 // get size of longest string
69 size_t s = 0;
70 for (auto &x : a) s = std::max(s, x.size() + 1);
71
72 // copy each string to a buffer and pad with zeros
73 std::vector<char> buf;
74 buf.resize(a.size() * s, 0x00);
75 size_t i = 0;
76 for (auto &x : a) {
77 strcpy(&buf[i * s], x.c_str());
78 ++i;
79 }
80
81 // return h5::char_buf
82 auto len = h5::v_t{size_t(a.size()), s};
83 return {buf, len};
84 }
85
86 // Write an h5::char_buf into a 1-dimensional nda::MemoryArray.
87 template <MemoryArrayOfRank<1> A>
88 void from_char_buf(h5::char_buf const &cb, A &a) {
89 // prepare the array
90 resize_or_check(a, std::array<long, 1>{static_cast<long>(cb.lengths[0])});
91
92 // loop over all strings
93 auto len_string = cb.lengths[1];
94 size_t i = 0;
95 for (auto &x : a) {
96 x = "";
97 x.append(&cb.buffer[i * len_string]);
98 ++i;
99 }
100 }
101
102 } // namespace detail
103
119 template <MemoryArray A>
120 void h5_write(h5::group g, std::string const &name, A const &a, bool compress = true) {
121 if constexpr (std::is_same_v<nda::get_value_t<A>, std::string>) {
122 // 1-dimensional array/view of strings
123 h5_write(g, name, detail::to_char_buf(a));
124 } else if constexpr (is_scalar_v<typename A::value_type>) {
125 // n-dimensional array/view of scalars
126 // make a copy if the array/view is not in C-order and write the copy
127 if (not a.indexmap().is_stride_order_C()) {
128 using h5_arr_t = nda::array<get_value_t<A>, A::rank>;
129 auto a_c_layout = h5_arr_t{a.shape()};
130 a_c_layout() = a;
131 h5_write(g, name, a_c_layout, compress);
132 return;
133 }
134
135 // prepare the h5::array_view and write it
136 auto v = detail::prepare_h5_array_view(a);
137 h5::array_interface::write(g, name, v, compress);
138 } else {
139 // n-dimensional array/view of some generic unknown type
140 auto g2 = g.create_group(name);
141 h5_write(g2, "shape", a.shape());
142 auto make_name = [](auto i0, auto... is) { return (std::to_string(i0) + ... + ("_" + std::to_string(is))); };
143 nda::for_each(a.shape(), [&](auto... is) { h5_write(g2, make_name(is...), a(is...)); });
144 }
145 }
146
162 template <size_t NDim, typename... IRs>
163 auto hyperslab_and_shape_from_slice(std::tuple<IRs...> const &slice, std::vector<h5::hsize_t> const &ds_shape, bool is_complex) {
164 // number of parameters specifying the slice
165 static constexpr auto size_of_slice = sizeof...(IRs);
166
167 // number of nda::ellipsis objects in the slice (only 1 is allowed)
168 static constexpr auto ellipsis_count = (std::is_same_v<IRs, ellipsis> + ... + 0);
169 static_assert(ellipsis_count < 2, "Error in nda::hyperslab_and_shape_from_slice: Only a single ellipsis is allowed in slicing");
170 static constexpr auto has_ellipsis = (ellipsis_count == 1);
171
172 // position of the ellipsis in the slice parameters
173 static constexpr auto ellipsis_position = [&]<size_t... Is>(std::index_sequence<Is...>) {
174 if constexpr (has_ellipsis) return ((std::is_same_v<IRs, ellipsis> * Is) + ... + 0);
175 return size_of_slice;
176 }(std::index_sequence_for<IRs...>{});
177
178 // number of integers in the slice parameters
179 static constexpr auto integer_count = (std::integral<IRs> + ... + 0);
180
181 // number of nda::range and nda::range::all_t objects in the slice parameters
182 static constexpr auto range_count = size_of_slice - integer_count - ellipsis_count;
183 static_assert((has_ellipsis && range_count <= NDim) || range_count == NDim,
184 "Error in nda::hyperslab_and_shape_from_slice: Rank does not match the number of non-trivial slice dimensions");
185
186 // number of dimensions spanned by the ellipsis
187 static constexpr auto ellipsis_width = NDim - range_count;
188
189 // number of dimensions in the hyperslab
190 static constexpr auto slab_rank = NDim + integer_count;
191
192 // check rank of the dataset (must match the rank of the hyperslabs)
193 auto ds_rank = ds_shape.size() - is_complex;
194 if (slab_rank != ds_rank)
195 NDA_RUNTIME_ERROR << "Error in nda::hyperslab_and_shape_from_slice: Incompatible dataset and slice ranks: " << ds_rank
196 << " != " << size_of_slice;
197
198 // create and return the hyperslab and the shape array
199 auto slab = h5::array_interface::hyperslab(slab_rank, is_complex);
200 auto shape = std::array<long, NDim>{};
201 [&, m = 0]<size_t... Is>(std::index_sequence<Is...>) mutable {
202 (
203 [&]<typename IR>(size_t n, IR const &ir) mutable {
204 if (n > ellipsis_position) n += (ellipsis_width - 1);
205 if constexpr (std::integral<IR>) {
206 slab.offset[n] = ir;
207 slab.count[n] = 1;
208 } else if constexpr (std::is_same_v<IR, nda::ellipsis>) {
209 for (auto k : range(n, n + ellipsis_width)) {
210 slab.count[k] = ds_shape[k];
211 shape[m++] = ds_shape[k];
212 }
213 } else if constexpr (std::is_same_v<IR, nda::range>) {
214 slab.offset[n] = ir.first();
215 slab.stride[n] = ir.step();
216 slab.count[n] = ir.size();
217 shape[m++] = ir.size();
218 } else {
219 static_assert(std::is_same_v<IR, nda::range::all_t>);
220 slab.count[n] = ds_shape[n];
221 shape[m++] = ds_shape[n];
222 }
223 }(Is, std::get<Is>(slice)),
224 ...);
225 }(std::make_index_sequence<size_of_slice>{});
226 return std::make_pair(slab, shape);
227 }
228
245 template <MemoryArray A, typename... IRs>
246 void h5_write(h5::group g, std::string const &name, A const &a, std::tuple<IRs...> const &slice) {
247 // compile-time checks
248 constexpr int size_of_slice = sizeof...(IRs);
249 static_assert(size_of_slice > 0, "Error in nda::h5_write: Invalid empty slice");
250 static_assert(is_scalar_v<typename A::value_type>, "Error in nda::h5_write: Slicing is only supported for scalar types");
251 static constexpr bool is_complex = is_complex_v<typename A::value_type>;
252
253 // make a copy if the array/view is not in C-order and write the copy
254 if (not a.indexmap().is_stride_order_C()) {
255 using h5_arr_t = nda::array<get_value_t<A>, A::rank>;
256 auto a_c_layout = h5_arr_t{a.shape()};
257 a_c_layout() = a;
258 h5_write(g, name, a_c_layout, slice);
259 return;
260 }
261
262 // get dataset info and check that the datatypes of the dataset and the array match
263 auto ds_info = h5::array_interface::get_dataset_info(g, name);
264 if (is_complex != ds_info.has_complex_attribute)
265 NDA_RUNTIME_ERROR << "Error in nda::h5_write: Dataset and array/view must both be complex or non-complex";
266
267 // get hyperslab and shape from the slice and check that the shapes match
268 auto const [sl, sh] = hyperslab_and_shape_from_slice<A::rank>(slice, ds_info.lengths, is_complex);
269 if (sh != a.shape()) NDA_RUNTIME_ERROR << "Error in nda::h5_write: Incompatible slice and array shape: " << sh << " != " << a.shape();
270
271 // prepare the h5::array_view and write it
272 auto v = detail::prepare_h5_array_view(a);
273 h5::array_interface::write_slice(g, name, v, sl);
274 }
275
297 template <MemoryArray A, typename... IRs>
298 void h5_read(h5::group g, std::string const &name, A &a, std::tuple<IRs...> const &slice = {}) {
299 constexpr bool slicing = (sizeof...(IRs) > 0);
300
301 if constexpr (std::is_same_v<typename A::value_type, std::string>) {
302 // 1-dimensional array/view of strings
303 static_assert(!slicing, "Error in nda::h5_read: Slicing not supported for arrays/views of strings");
304 h5::char_buf cb;
305 h5_read(g, name, cb);
306 detail::from_char_buf(cb, a);
307 } else if constexpr (is_scalar_v<typename A::value_type>) {
308 // n-dimensional array/view of scalars
309 // read into a temporary array if the array/view is not in C-order and copy the elements
310 if (not a.indexmap().is_stride_order_C()) {
312 auto a_c_layout = h5_arr_t{};
313 h5_read(g, name, a_c_layout, slice);
314 detail::resize_or_check(a, a_c_layout.shape());
315 a() = a_c_layout;
316 return;
317 }
318
319 // get dataset info
320 auto ds_info = h5::array_interface::get_dataset_info(g, name);
321
322 // allow to read non-complex data into a complex array
323 static constexpr bool is_complex = is_complex_v<typename A::value_type>;
324 if constexpr (is_complex) {
325 if (!ds_info.has_complex_attribute) {
327 h5_read(g, name, tmp, slice);
328 a = tmp;
329 return;
330 }
331 }
332
333 // get the hyperslab and the shape of the slice
334 std::array<long, A::rank> shape{};
335 auto slab = h5::array_interface::hyperslab{};
336 if constexpr (slicing) {
337 auto const [sl, sh] = hyperslab_and_shape_from_slice<A::rank>(slice, ds_info.lengths, is_complex);
338 slab = sl;
339 shape = sh;
340 } else {
341 for (int u = 0; u < A::rank; ++u) shape[u] = ds_info.lengths[u]; // NB : correct for complex
342 }
343
344 // resize the array or check that the shape matches
345 detail::resize_or_check(a, shape);
346
347 // check the rank of the dataset and the array
348 auto ds_rank = ds_info.rank() - is_complex;
349 if (!slicing && ds_rank != A::rank)
350 NDA_RUNTIME_ERROR << "Error in nda::h5_read: Incompatible dataset and array ranks: " << ds_rank << " != " << A::rank;
351
352 // prepare the h5::array_view and read into it
353 auto v = detail::prepare_h5_array_view(a);
354 h5::array_interface::read(g, name, v, slab);
355 } else {
356 // n-dimensional array/view of some generic unknown type
357 static_assert(!slicing, "Error in nda::h5_read: Slicing not supported for arrays/views of generic types");
358 auto g2 = g.open_group(name);
359
360 // get and check the shape or resize the array
361 std::array<long, A::rank> h5_shape;
362 h5_read(g2, "shape", h5_shape);
363 detail::resize_or_check(a, h5_shape);
364
365 // read element-by-element using the appropriate h5_read implementation
366 auto make_name = [](auto i0, auto... is) { return (std::to_string(i0) + ... + ("_" + std::to_string(is))); };
367 nda::for_each(a.shape(), [&](auto... is) { h5_read(g2, make_name(is...), a(is...)); });
368 }
369 }
370
372
373} // namespace nda
Provides concepts for the nda library.
Provides various convenient aliases and helper functions for nda::basic_array and nda::basic_array_vi...
Provides a custom runtime error class and macros to assert conditions and throw exceptions.
Provides for_each functions for multi-dimensional arrays/views.
void h5_read(h5::group g, std::string const &name, A &a, std::tuple< IRs... > const &slice={})
Read into an nda::MemoryArray from an HDF5 file.
Definition h5.hpp:298
void h5_write(h5::group g, std::string const &name, A const &a, bool compress=true)
Write an nda::MemoryArray to a new dataset/subgroup into an HDF5 file.
Definition h5.hpp:120
auto hyperslab_and_shape_from_slice(std::tuple< IRs... > const &slice, std::vector< h5::hsize_t > const &ds_shape, bool is_complex)
Construct an h5::array_interface::hyperslab and the corresponding shape from a given slice,...
Definition h5.hpp:163
basic_array< ValueType, Rank, Layout, 'A', ContainerPolicy > array
Alias template of an nda::basic_array with an 'A' algebra.
__inline__ void for_each(std::array< Int, R > const &shape, F &&f)
Loop over all possible index values of a given shape and apply a function to them.
Definition for_each.hpp:116
std::string to_string(std::array< T, R > const &a)
Get a string representation of a std::array.
Definition array.hpp:52
constexpr bool is_complex_v
Constexpr variable that is true if type T is a std::complex type.
Definition traits.hpp:65
constexpr bool is_scalar_v
Constexpr variable that is true if type S is a scalar type, i.e. arithmetic or complex.
Definition traits.hpp:69
Includes the itertools header and provides some additional utilities.
Provides type traits for the nda library.