ATLAS Offline Software
Loading...
Searching...
No Matches
Writer.h
Go to the documentation of this file.
1// this is -*- C++ -*-
2/*
3 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
4*/
5#ifndef HDF_TUPLE_HH
6#define HDF_TUPLE_HH
7
15
16#include "WriterConfiguration.h"
17#include "H5Traits.h"
18#include "CompressedTypes.h"
19#include "WriterCommon.h"
20#include "defaults.h"
21
22#include "H5Cpp.h"
23
24#include <functional>
25#include <vector>
26#include <memory>
27#include <cassert>
28#include <set>
29#include <mutex>
30#include <utility>
31
32namespace H5Utils {
33
36
37 namespace internal {
38
47 template <typename I>
49 {
50 public:
51 virtual ~IDataConsumer() {}
52 virtual data_buffer_t getBuffer(I) const = 0;
53 virtual data_buffer_t getDefault() const = 0;
54 virtual H5::DataType getType() const = 0;
55 virtual H5::DataType getWriteType() const = 0;
56 virtual std::string name() const = 0;
57 typedef I input_type;
58 };
59
61 template <typename T, typename I>
62 class DataConsumer: public IDataConsumer<I>
63 {
64 public:
65 DataConsumer(const std::string&,
66 const std::function<T(I)>&,
67 const T default_value = T(),
69 data_buffer_t getBuffer(I) const override;
70 data_buffer_t getDefault() const override;
71 H5::DataType getType() const override;
72 H5::DataType getWriteType() const override;
73 std::string name() const override;
74 private:
75 std::function<T(I)> m_getter;
76 std::string m_name;
78 H5::DataType m_write_type;
79 };
80 template <typename T, typename I>
82 const std::function<T(I)>& func,
83 const T default_value,
84 Compression comp):
85 m_getter(func),
86 m_name(name),
87 m_default_value(default_value),
89 {
90 }
91 template <typename T, typename I>
93 data_buffer_t buffer;
94 H5Traits<T>::ref(buffer) = m_getter(args);
95 return buffer;
96 }
97 template <typename T, typename I>
99 data_buffer_t default_value;
100 H5Traits<T>::ref(default_value) = m_default_value;
101 return default_value;
102 }
103 template <typename T, typename I>
104 H5::DataType DataConsumer<T, I>::getType() const {
105 return H5Traits<T>::type;
106 }
107 template <typename T, typename I>
108 H5::DataType DataConsumer<T, I>::getWriteType() const {
109 return m_write_type;
110 }
111 template <typename T, typename I>
112 std::string DataConsumer<T, I>::name() const {
113 return m_name;
114 }
115
116 }
118
127 template <typename I>
128 using SharedConsumer = std::shared_ptr<internal::IDataConsumer<I> >;
129 template <typename I>
131 {
132 public:
133
135 template <typename T>
136 void add(const std::string& name, const std::function<T(I)>&,
137 const T& default_value = T(),
139
141 template <
142 typename F,
143 typename R=decltype(std::declval<F>()(std::declval<I>())),
144 typename T=R
145 >
146 void add(const std::string& name, const F func, const T& def = T(),
148 add<R>(name, std::function<R(I)>(std::move(func)), R(def), comp);
149 }
150
151
152 const std::vector<SharedConsumer<I> > & getConsumers() const;
153
154 using input_type = I;
155 template <typename T>
156 using function_type = std::function<T(I)>;
157
158 private:
159 std::vector<SharedConsumer<I> > m_consumers;
160 std::set<std::string> m_used;
161 };
162
163
164 template <typename I>
165 template <typename T>
166 void Consumers<I>::add(const std::string& name,
167 const std::function<T(I)>& fun,
168 const T& def_val,
169 Compression comp)
170 {
171 if (m_used.count(name)) {
172 throw std::logic_error("tried to insert '" + name + "' twice");
173 }
174 m_consumers.push_back(
175 std::make_shared<internal::DataConsumer<T,I>>(name, fun, def_val, comp));
176 m_used.insert(name);
177 }
178
179 template <typename I>
180 const std::vector<SharedConsumer<I>> & Consumers<I>::getConsumers() const {
181 return m_consumers;
182 }
183
184
187 namespace internal {
188
189 // This class exists to inspect the way the fill(...) function is
190 // called, so that errors can be caught with a static assert and
191 // give a much less cryptic error message.
192 //
193 // We check to make sure the depth of the input matches the rank
194 // of the output, and that the types match.
195 using std::begin;
196 template <size_t N, typename T, typename I, typename = void>
197 struct CheckType {
198 static const bool ok_type = std::is_convertible<T,I>::value;
199 static const bool any_type = ok_type;
200 static const int depth = 0;
201 };
202 template <size_t N, typename T, typename I>
203 struct CheckType <N,T,I,decltype(*begin(std::declval<T&>()), void())> {
204 typedef CheckType<N-1,decltype(*begin(std::declval<T&>())),I> subtype;
205 static const bool ok_type = subtype::ok_type;
206 static const bool any_type = (
207 subtype::any_type || std::is_convertible<T,I>::value);
208 static const int depth = subtype::depth + 1;
209 };
210 template <typename T, typename I>
211 struct CheckType <0,T,I,decltype(*begin(std::declval<T&>()), void())> {
213 static const bool ok_type = std::is_convertible<T,I>::value;
214 static const bool any_type = subtype::any_type || ok_type;
215 static const int depth = subtype::depth + 1;
216 };
217
225 template <size_t N, typename F, typename T, size_t M = N>
227
228 std::vector<data_buffer_t> buffer;
229 std::vector<std::array<hsize_t, N> > element_offsets;
230 DataFlattener(const F& filler, T args,
231 const std::array<hsize_t,M>& extent):
232 buffer() {
233 hsize_t offset = 0;
234 for (const auto& arg: args) {
235 const size_t this_dim_max = extent.at(extent.size() - N);
236 if (offset >= this_dim_max) return;
237 DataFlattener<N-1, F, decltype(arg), M> in(filler, arg, extent);
238 buffer.insert(buffer.end(), in.buffer.begin(), in.buffer.end());
239 for (const auto& in_ele: in.element_offsets){
240 std::array<hsize_t, N> element_pos;
241 element_pos.at(0) = offset;
242 std::copy(in_ele.begin(), in_ele.end(), element_pos.begin() + 1);
243 element_offsets.push_back(element_pos);
244 }
245 offset++;
246 }
247 }
248 };
249 template <typename F, typename T, size_t M>
250 struct DataFlattener<0, F, T, M> {
251 std::vector<data_buffer_t> buffer;
252 std::vector<std::array<hsize_t, 0> > element_offsets;
253 DataFlattener(const F& f, T args,
254 const std::array<hsize_t,M>& /*extent*/):
255 buffer(),
256 element_offsets(1) {
257 for (const auto& filler: f) {
258 buffer.push_back(filler->getBuffer(args));
259 }
260 }
261 };
262
265 template<typename I>
266 H5::CompType buildType(const std::vector<SharedConsumer<I> >& consumers) {
267 if (consumers.size() < 1) {
268 throw std::logic_error(
269 "you must specify at least one consumer when initializing the HDF5"
270 "writer");
271 }
272
273 H5::CompType type(consumers.size() * sizeof(data_buffer_t));
274 size_t dt_offset = 0;
275 for (const SharedConsumer<I>& filler: consumers) {
276 type.insertMember(filler->name(), dt_offset, filler->getType());
277 dt_offset += sizeof(data_buffer_t);
278 }
279 return type;
280 }
281 template<typename I>
282 H5::CompType buildWriteType(const std::vector<SharedConsumer<I> >& con) {
283 H5::CompType type(con.size() * sizeof(data_buffer_t));
284 size_t dt_offset = 0;
285 for (const SharedConsumer<I>& filler: con) {
286 type.insertMember(filler->name(), dt_offset, filler->getWriteType());
287 dt_offset += sizeof(data_buffer_t);
288 }
289 type.pack();
290 return type;
291 }
292
294 template <typename I, size_t N>
296 DSParameters(const std::vector<SharedConsumer<I> >& fillers,
297 const std::array<hsize_t,N>& extent,
298 hsize_t batch_size);
299 H5::CompType type{};
300 std::array<hsize_t,N> extent{};
301 hsize_t batch_size{};
302 };
303
304 // DS parameters
305 template <typename I, size_t N>
307 const std::array<hsize_t,N>& extent_,
308 hsize_t batch_size_):
309 type(buildType(cons)),
310 extent(extent_),
311 batch_size(batch_size_)
312 {
313 }
314
315
316 template<typename I>
317 std::vector<data_buffer_t> buildDefault(const std::vector<SharedConsumer<I> >& f) {
318 std::vector<data_buffer_t> def;
319 for (const SharedConsumer<I>& filler: f) {
320 def.push_back(filler->getDefault());
321 }
322 return def;
323 }
324
325 // some internal functions take a vector, others take arrays
326 template <size_t N>
327 std::vector<hsize_t> vec(std::array<hsize_t,N> a) {
328 return std::vector<hsize_t>(a.begin(),a.end());
329 }
330
331 // default initalizer for writers where the extent isn't specified
332 template <hsize_t N>
333 std::array<hsize_t, N> uniform(size_t val) {
334 std::array<hsize_t, N> ar;
335 ar.fill(val);
336 //coverity[UNINIT:FALSE]
337 return ar;
338 }
339
340
341 }
343
351 template <size_t N, typename I>
352 class Writer {
353 public:
354 Writer(H5::Group& group, const std::string& name,
355 const Consumers<I>& consumers,
356 const std::array<hsize_t, N>& extent = internal::uniform<N>(5),
357 hsize_t batch_size = defaults::batch_size);
358 Writer(H5::Group& group,
359 const Consumers<I>& consumers,
361 Writer(const Writer&) = delete;
362 Writer(Writer&&) noexcept;
363 Writer& operator=(Writer&) = delete;
365 template <typename T>
366 void fill(T);
367 void flush();
368 size_t index() const;
370 using input_type = I;
371 template <typename T>
372 using function_type = typename consumer_type::template function_type<T>;
374 private:
375 Writer(Writer&& other, std::unique_lock<std::recursive_mutex> other_lock) noexcept;
376 const internal::DSParameters<I,N> m_par;
377 hsize_t m_offset;
379 std::vector<internal::data_buffer_t> m_buffer;
381 H5::DataSet m_ds;
382 H5::DataSpace m_file_space;
383 std::recursive_mutex m_mutex;
384 };
385
386 template <size_t N, typename I>
387 Writer<N, I>::Writer(H5::Group& group, const std::string& name,
388 const Consumers<I>& consumers,
389 const std::array<hsize_t,N>& extent,
390 hsize_t batch_size):
391 Writer<N,I>(
392 group, consumers, WriterConfiguration<N>{
393 .name = name, // name
394 .extent = extent, // extent
395 .batch_size = batch_size, // batch_size
396 .chunks = extent, // chunks
397 .deflate = defaults::deflate, // deflate
398 .plist_callbacks = {} // plist_callbacks
399 })
400 {}
401
402 template <size_t N, typename I>
403 Writer<N, I>::Writer(H5::Group& group,
404 const Consumers<I>& consumers,
405 const WriterConfiguration<N>& cfg):
406 m_par(consumers.getConsumers(), cfg.extent,
407 cfg.batch_size ? *cfg.batch_size : defaults::batch_size),
408 m_offset(0),
409 m_buffer_rows(0),
410 m_consumers(consumers.getConsumers()),
411 m_file_space(H5S_SIMPLE)
412 {
414 if (m_par.batch_size < 1) {
415 throw std::logic_error("batch size must be > 0");
416 }
417 // create space
418 H5::DataSpace space = internal::getUnlimitedSpace(
419 internal::vec(cfg.extent));
420
421 // create params
422 H5::DSetCreatPropList params = internal::getChunckedDatasetParams(cfg);
423 std::vector<data_buffer_t> default_value = internal::buildDefault(
424 consumers.getConsumers());
425 params.setFillValue(m_par.type, default_value.data());
426
427 // create ds
428 internal::throwIfExists(cfg.name, group);
429 H5::CompType packed_type = buildWriteType(consumers.getConsumers());
430 m_ds = group.createDataSet(cfg.name, packed_type, space, params);
431 m_file_space = m_ds.getSpace();
432 m_file_space.selectNone();
433 }
434
435 // the mutex isn't movable, the moved-from writer is left empty; don't move a writer other threads still use
436 template <size_t N, typename I>
437 Writer<N, I>::Writer(Writer&& other) noexcept:
438 Writer(std::move(other), std::unique_lock(other.m_mutex))
439 {
440 }
441
442 template <size_t N, typename I>
443 Writer<N, I>::Writer(Writer&& other, std::unique_lock<std::recursive_mutex>) noexcept:
444 m_par(other.m_par),
445 m_offset(std::exchange(other.m_offset, 0)),
446 m_buffer_rows(std::exchange(other.m_buffer_rows, 0)),
447 m_buffer(std::exchange(other.m_buffer, {})),
448 m_consumers(std::move(other.m_consumers)),
449 m_ds(std::move(other.m_ds)),
450 m_file_space(std::move(other.m_file_space))
451 {
452 }
453
454 template <size_t N, typename I>
456 using namespace H5Utils;
457 try {
458 flush();
459 } catch (H5::Exception& err) {
460 internal::printDestructorError(err.getDetailMsg());
461 } catch (std::exception& err) {
463 }
464 }
465
466 template <size_t N, typename I>
467 template <typename T>
468 void Writer<N, I>::fill(T arg) {
469 // lock witin a scope here to check the buffer size and
470 // (potentially) flush, but we can release it to compute the
471 // output array since that's thread local
472 {
473 std::lock_guard lock(m_mutex);
474 if (m_buffer_rows == m_par.batch_size) {
475 flush();
476 }
477 }
478
479 // make some assertions to simplify debugging, the errors can be
480 // pretty nasty wtih all these templates.
481 typedef internal::CheckType<N, T, I> checkType;
482 static_assert(
483 checkType::depth >= N,
484 "\n\n"
485 " ** H5 Writer rank is greater than the depth of fill(...) input! **"
486 " \n");
487 static_assert(
488 // Suppress cppcheck warning here. It reports that the condition
489 // below is always true if `N = 0`. In N > 0 cases this
490 // condition might be false though, so I consider this a
491 // spurious warning.
492 //
493 // <3 Dan Guest, 2024-07-16 <3
494 //
495 // cppcheck-suppress incorrectLogicOperator
496 !(checkType::any_type && !checkType::ok_type),
497 "\n\n"
498 " ** H5 Writer input type matches fill(...), but rank is incorrect! **"
499 " \n");
500 static_assert(
501 checkType::any_type,
502 "\n\n"
503 " ** H5 Writer input type doesn't match input for fill(...)! **"
504 " \n");
505
506 internal::DataFlattener<N, decltype(m_consumers), T> buf(
507 m_consumers, std::move(arg), m_par.extent);
508 hsize_t n_el = buf.element_offsets.size();
509 std::vector<hsize_t> elements;
510
511 // lock again here since we're done with the local stuff: there's
512 // some access to class variables below which we need to do one
513 // thread at a time.
514 std::lock_guard lock(m_mutex);
515 for (const auto& el_local: buf.element_offsets) {
516 std::array<hsize_t, N+1> el_global;
517 el_global[0] = m_offset + m_buffer_rows;
518 std::copy(el_local.begin(), el_local.end(), el_global.begin() + 1);
519 elements.insert(elements.end(), el_global.begin(), el_global.end());
520 }
521 if (n_el > 0) {
522 m_file_space.selectElements(H5S_SELECT_APPEND, n_el, elements.data());
523 }
524 m_buffer.insert(m_buffer.end(), buf.buffer.begin(), buf.buffer.end());
526 }
527
528 template <size_t N, typename I>
530 std::lock_guard lock(m_mutex);
531 const hsize_t buffer_size = m_buffer_rows;
532 if (buffer_size == 0) return;
533
534 // extend the ds
535 std::vector<hsize_t> total_dims{buffer_size + m_offset};
536 total_dims.insert(total_dims.end(),
537 m_par.extent.begin(),
538 m_par.extent.end());
539 m_ds.extend(total_dims.data());
540 m_file_space.setExtentSimple(total_dims.size(), total_dims.data());
541
542 // write out
543 hsize_t n_buffer_pts = m_buffer.size() / m_consumers.size();
544 assert(m_file_space.getSelectNpoints() >= 0);
545 assert(static_cast<hsize_t>(m_file_space.getSelectNpoints())
546 == n_buffer_pts);
547 H5::DataSpace mem_space(1, &n_buffer_pts);
548 m_ds.write(m_buffer.data(), m_par.type, mem_space, m_file_space);
549 m_offset += buffer_size;
550 m_buffer.clear();
551 m_buffer_rows = 0;
552 m_file_space.selectNone();
553 }
554
555 template <size_t N, typename I>
556 size_t Writer<N, I>::index() const {
557 return m_buffer_rows + m_offset;
558 }
559
570 template <size_t N, class I>
572 H5::Group& group, const std::string& name,
573 const Consumers<I>& consumers,
574 const std::array<hsize_t, N>& extent = internal::uniform<N>(5),
575 hsize_t batch_size = defaults::batch_size) {
577 config.name = name;
578 config.extent = extent;
579 config.batch_size = batch_size;
580 return Writer<N,I>(group, consumers, config);
581 }
582 template <size_t N, class I>
584 H5::Group& group,
585 const Consumers<I>& consumers,
586 const WriterConfiguration<N>& config) {
587 return Writer<N,I>(group, consumers, config);
588 }
589
596 template <typename T>
598
599
606 template <size_t N, typename T>
608
609
616 template <typename T>
618
619}
620
621#endif
std::vector< size_t > vec
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
static Double_t a
#define F(x, y, z)
Definition MD5.cxx:112
#define I(x, y, z)
Definition MD5.cxx:116
const std::vector< SharedConsumer< I > > & getConsumers() const
Definition Writer.h:180
std::function< T(I)> function_type
Definition Writer.h:156
void add(const std::string &name, const std::function< T(I)> &, const T &default_value=T(), Compression=Compression::STANDARD)
This should be the only method you need in this class.
Definition Writer.h:166
std::vector< SharedConsumer< const T & > > m_consumers
Definition Writer.h:159
std::set< std::string > m_used
Definition Writer.h:160
void add(const std::string &name, const F func, const T &def=T(), Compression comp=Compression::STANDARD)
overload to cast lambdas into functions
Definition Writer.h:146
Writer.
Definition Writer.h:352
Writer(const Writer &)=delete
Writer(Writer &&) noexcept
Definition Writer.h:437
std::vector< internal::data_buffer_t > m_buffer
Definition Writer.h:379
std::vector< SharedConsumer< const T & > > m_consumers
Definition Writer.h:380
Writer(H5::Group &group, const Consumers< I > &consumers, const WriterConfiguration< N > &=WriterConfiguration< N >())
Definition Writer.h:403
const internal::DSParameters< const T &, N > m_par
Definition Writer.h:376
Writer(H5::Group &group, const std::string &name, const Consumers< I > &consumers, const std::array< hsize_t, N > &extent=internal::uniform< N >(5), hsize_t batch_size=defaults::batch_size)
Definition Writer.h:387
std::recursive_mutex m_mutex
Definition Writer.h:383
Consumers< I > consumer_type
Definition Writer.h:369
WriterConfiguration< N > configuration_type
Definition Writer.h:373
size_t index() const
Definition Writer.h:556
typename consumer_type::template function_type< T > function_type
Definition Writer.h:372
implementation for variable filler
Definition Writer.h:63
data_buffer_t getDefault() const override
Definition Writer.h:98
DataConsumer(const std::string &, const std::function< T(I)> &, const T default_value=T(), Compression=Compression::STANDARD)
Definition Writer.h:81
H5::DataType getWriteType() const override
Definition Writer.h:108
H5::DataType getType() const override
Definition Writer.h:104
std::string name() const override
Definition Writer.h:112
data_buffer_t getBuffer(I) const override
Definition Writer.h:92
std::function< T(I)> m_getter
Definition Writer.h:75
DataConsumer classes.
Definition Writer.h:49
virtual std::string name() const =0
virtual data_buffer_t getDefault() const =0
virtual H5::DataType getWriteType() const =0
virtual data_buffer_t getBuffer(I) const =0
virtual H5::DataType getType() const =0
const hsize_t batch_size
Definition defaults.h:9
const int deflate
Definition defaults.h:10
clssses to add type traits for H5
H5::DSetCreatPropList getChunckedDatasetParams(const WriterConfiguration< N > &)
H5::CompType buildWriteType(const std::vector< SharedConsumer< I > > &con)
Definition Writer.h:282
std::vector< hsize_t > vec(std::array< hsize_t, N > a)
Definition Writer.h:327
H5::DataType getCompressedType(Compression comp)
H5::DataSpace getUnlimitedSpace(const std::vector< hsize_t > &max_length)
std::array< hsize_t, N > uniform(size_t val)
Definition Writer.h:333
void printDestructorError(const std::string &msg)
const H5::DataType H5Traits< int >::type
Definition H5Traits.cxx:16
void throwIfExists(const std::string &name, const H5::Group &in_group)
std::vector< data_buffer_t > buildDefault(const std::vector< SharedConsumer< I > > &f)
Definition Writer.h:317
H5::CompType buildType(const std::vector< SharedConsumer< I > > &consumers)
Adapter to translate configuration info into the objects needed by the writer.
Definition Writer.h:266
HDF5 Tuple Writer.
std::shared_ptr< internal::IDataConsumer< I > > SharedConsumer
Consumer Class.
Definition Writer.h:128
Writer< N, const T & > CRefWriter
CRefWriter.
Definition Writer.h:607
Writer< N, I > makeWriter(H5::Group &group, const std::string &name, const Consumers< I > &consumers, const std::array< hsize_t, N > &extent=internal::uniform< N >(5), hsize_t batch_size=defaults::batch_size)
makeWriter
Definition Writer.h:571
Writer< 0, const T & > SimpleWriter
SimpleWriter.
Definition Writer.h:617
Consumers< const T & > CRefConsumer
CRefConsumer.
Definition Writer.h:597
HDF5 Traits.
Definition H5Traits.h:17
Definition index.py:1
STL namespace.
CheckType< 0, decltype(*begin(std::declval< T & >())), I > subtype
Definition Writer.h:212
CheckType< N-1, decltype(*begin(std::declval< T & >())), I > subtype
Definition Writer.h:204
std::array< hsize_t, N > extent
Definition Writer.h:300
DSParameters(const std::vector< SharedConsumer< I > > &fillers, const std::array< hsize_t, N > &extent, hsize_t batch_size)
Definition Writer.h:306
std::vector< data_buffer_t > buffer
Definition Writer.h:251
DataFlattener(const F &f, T args, const std::array< hsize_t, M > &)
Definition Writer.h:253
std::vector< std::array< hsize_t, 0 > > element_offsets
Definition Writer.h:252
Data flattener class: this is used by the writer to read in the elements one by one and put them in a...
Definition Writer.h:226
DataFlattener(const F &filler, T args, const std::array< hsize_t, M > &extent)
Definition Writer.h:230
std::vector< data_buffer_t > buffer
Definition Writer.h:228
std::vector< std::array< hsize_t, N > > element_offsets
Definition Writer.h:229
We have lots of code to get around HDF5's rather weak typing.
Definition H5Traits.h:54
consumer_t getConsumers()
#define private
Definition testRead.cxx:27