openPMD-api
Attributable.hpp
1 /* Copyright 2017-2025 Fabian Koller, Axel Huebl, Franz Poeschel
2  *
3  * This file is part of openPMD-api.
4  *
5  * openPMD-api is free software: you can redistribute it and/or modify
6  * it under the terms of of either the GNU General Public License or
7  * the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * openPMD-api is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License and the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * and the GNU Lesser General Public License along with openPMD-api.
19  * If not, see <http://www.gnu.org/licenses/>.
20  */
21 #pragma once
22 
23 #include "openPMD/Error.hpp"
24 #include "openPMD/IO/AbstractIOHandler.hpp"
25 #include "openPMD/ThrowError.hpp"
26 #include "openPMD/auxiliary/OutOfRangeMsg.hpp"
27 #include "openPMD/backend/Attribute.hpp"
28 #include "openPMD/backend/Writable.hpp"
29 
30 #include <cstddef>
31 #include <map>
32 #include <memory>
33 #include <optional>
34 #include <string>
35 #include <type_traits>
36 #include <vector>
37 
38 // expose private and protected members for invasive testing
39 #ifndef OPENPMD_protected
40 #define OPENPMD_protected protected:
41 #endif
42 
43 namespace openPMD
44 {
45 namespace traits
46 {
47  template <typename T>
48  struct GenerationPolicy;
49 } // namespace traits
50 class AbstractFilePosition;
51 class Attributable;
52 class Iteration;
53 class Series;
54 
55 namespace internal
56 {
57  class IterationData;
58  class SeriesData;
59  struct HomogenizeExtents;
60 
62  {
63  friend class openPMD::Attributable;
64 
65  public:
69  virtual ~SharedAttributableData() = default;
70 
72  operator=(SharedAttributableData const &) = delete;
73  SharedAttributableData &operator=(SharedAttributableData &&) = delete;
74 
75  using A_MAP = std::map<std::string, Attribute>;
82 
86  A_MAP m_attributes;
87  };
88 
89  /*
90  * This is essentially a two-level pointer.
91  *
92  * 1. level: Our public API hands out handles to users that are (shared)
93  * pointers to an internal object (PIMPL).
94  * 2. level: Multiple internal objects might refer to the same item in an
95  * openPMD file, e.g. to the same backend object.
96  * So, the internal object for an Attributable is a shared pointer to the
97  * unique object identifying this item.
98  *
99  * Such sharing occurs in the CustomHierarchy class where multiple
100  * containers refer to the same group in the openPMD hierarchy
101  * (container of groups, of meshes, of particle species, of datasets).
102  * This might also become relevant for links as in HDF5 if we choose to
103  * implement them.
104  */
105 
106  class AttributableData : public std::shared_ptr<SharedAttributableData>
107  {
108  friend class openPMD::Attributable;
109 
110  using SharedData_t = std::shared_ptr<SharedAttributableData>;
111  using A_MAP = SharedData_t::element_type::A_MAP;
112 
113  public:
116  AttributableData(AttributableData const &) = delete;
117  AttributableData(AttributableData &&) = delete;
118  virtual ~AttributableData() = default;
119 
120  AttributableData &operator=(AttributableData const &) = delete;
121  AttributableData &operator=(AttributableData &&) = delete;
122 
123  // Make copies explicit, only to be used under the conditions described
124  // above
125  void cloneFrom(AttributableData const &other);
126 
127  template <typename T>
128  T asInternalCopyOf()
129  {
130  auto *self = dynamic_cast<typename T::Data_t *>(this);
131  if (!self)
132  {
133  if constexpr (std::is_same_v<Series, T>)
134  {
135  throw std::runtime_error(
136  "[Attributable::retrieveSeries] Error when trying to "
137  "retrieve the Series object. Note: An instance of the "
138  "Series object must still exist when flushing. A "
139  "common cause for this error is using a flush call on "
140  "a handle (e.g. `Iteration::seriesFlush()`) when the "
141  "original Series object has already gone out of "
142  "scope.");
143  }
144  else
145  {
146  throw std::runtime_error(
147 
148  "[AttributableData::asInternalCopyOf<T>] Error when "
149  "trying to retrieve a containing object. Note: An "
150  "instance of the Series object must still exist when "
151  "flushing. A common cause for this error is using a "
152  "flush call on a handle (e.g. "
153  "`Iteration::seriesFlush()`) when the original Series "
154  "object has already gone out of scope.");
155  }
156  }
157  T res;
158  res.setData(
159  std::shared_ptr<typename T::Data_t>(self, [](auto const *) {}));
160  return res;
161  }
162 
163  inline auto attributes() -> A_MAP &
164  {
165  return operator*().m_attributes;
166  }
167  [[nodiscard]] inline auto attributes() const -> A_MAP const &
168  {
169  return operator*().m_attributes;
170  }
171  [[nodiscard]] inline auto readAttribute(std::string const &name) const
172  -> Attribute const &
173  {
174  auto const &attr = attributes();
175  if (auto it = attr.find(name); it != attr.end())
176  {
177  return it->second;
178  }
179  else
180  {
181  throw error::ReadError(
182  error::AffectedObject::Attribute,
183  error::Reason::NotFound,
184  std::nullopt,
185  "Not found: '" + name + "'.");
186  }
187  }
188  };
189 
190  template <typename, typename>
191  class BaseRecordData;
192 
193  class RecordComponentData;
194 
195  /*
196  * Internal function to turn a handle into an owning handle that will keep
197  * not only itself, but the entire Series alive. Works by hiding a copy of
198  * the Series into the destructor lambda of the internal shared pointer. The
199  * returned handle is entirely safe to use in just the same ways as a normal
200  * handle, just the surrounding Series needs not be kept alive any more
201  * since it is stored within the handle. By storing the Series in the
202  * handle, not in the actual data, reference cycles are avoided.
203  *
204  * Instantiations for T exist for types RecordComponent,
205  * MeshRecordComponent, Mesh, Record, ParticleSpecies, Iteration.
206  */
207  template <typename T>
208  T &makeOwning(T &self, Series);
209 } // namespace internal
210 
211 namespace debug
212 {
213  void printDirty(Series const &);
214 }
215 
222 {
223  // @todo remove unnecessary friend (wew that sounds bitter)
224  using A_MAP = std::map<std::string, Attribute>;
225  friend Writable *getWritable(Attributable *);
226  template <typename T_elem>
227  friend class BaseRecord;
228  template <typename T_elem>
229  friend class BaseRecordInterface;
230  template <typename, typename>
231  friend class internal::BaseRecordData;
232  template <typename T, typename T_key, typename T_container>
233  friend class Container;
234  template <typename T>
235  friend struct traits::GenerationPolicy;
236  friend class Iteration;
237  friend class Series;
238  friend class Writable;
239  friend class internal::RecordComponentData;
240  friend void debug::printDirty(Series const &);
241  template <typename T>
242  friend T &internal::makeOwning(T &self, Series);
243  friend class StatefulSnapshotsContainer;
244  friend class internal::AttributableData;
245  friend class Snapshots;
246  friend struct internal::HomogenizeExtents;
247 
248 protected:
249  // tag for internal constructor
250  struct NoInit
251  {};
252 
254  std::shared_ptr<Data_t> m_attri;
255 
256 public:
257  Attributable();
258  Attributable(NoInit) noexcept;
259 
260  virtual ~Attributable() = default;
261 
276  template <typename T>
277  bool setAttribute(std::string const &key, T value);
278  bool setAttribute(std::string const &key, char const value[]);
289  Attribute getAttribute(std::string const &key) const;
290 
297  bool deleteAttribute(std::string const &key);
298 
303  std::vector<std::string> attributes() const;
308  size_t numAttributes() const;
314  bool containsAttribute(std::string const &key) const;
315 
321  std::string comment() const;
328  Attributable &setComment(std::string const &comment);
329 
344  void seriesFlush(std::string backendConfig = "{}");
345 
358  void iterationFlush(std::string backendConfig = "{}");
359 
366  struct MyPath
367  {
368  std::string directory;
369  std::string seriesName;
370  std::string seriesExtension;
379  std::vector<std::string> group;
380  Access access;
381 
383  std::string filePath() const;
385  std::string openPMDPath() const;
386  };
387 
393  MyPath myPath() const;
394 
399  void touch();
400 
401  [[nodiscard]] OpenpmdStandard openPMDStandard() const;
402 
403  // clang-format off
404 OPENPMD_protected
405  // clang-format on
406 
407  Series retrieveSeries() const;
408 
416  [[nodiscard]] auto containingIteration() const -> std::pair<
417  std::optional<internal::IterationData const *>,
418  internal::SeriesData const *>;
419  auto containingIteration() -> std::
420  pair<std::optional<internal::IterationData *>, internal::SeriesData *>;
423  template <bool flush_entire_series>
424  void seriesFlush_impl(internal::FlushParams const &);
425 
426  void flushAttributes(internal::FlushParams const &);
427 
428  enum ReadMode
429  {
445  };
446  void readAttributes(ReadMode);
447 
468  template <typename T>
469  T readFloatingpoint(std::string const &key) const;
491  template <typename T>
492  std::vector<T> readVectorFloatingpoint(std::string const &key) const;
493 
494  /* views into the resources held by m_writable
495  * purely for convenience so code that uses these does not have to go
496  * through m_writable-> */
497  AbstractIOHandler *IOHandler()
498  {
499  return const_cast<AbstractIOHandler *>(
500  static_cast<Attributable const *>(this)->IOHandler());
501  }
502  AbstractIOHandler const *IOHandler() const
503  {
504  auto &opt = writable().IOHandler;
505  if (!opt || !opt->has_value())
506  {
507  return nullptr;
508  }
509  return &*opt->value();
510  }
511  Writable *&parent()
512  {
513  return writable().parent;
514  }
515  Writable const *parent() const
516  {
517  return writable().parent;
518  }
519  Writable &writable()
520  {
521  return (*m_attri)->m_writable;
522  }
523  Writable const &writable() const
524  {
525  return (*m_attri)->m_writable;
526  }
527 
528  inline void setData(std::shared_ptr<internal::AttributableData> attri)
529  {
530  m_attri = std::move(attri);
531  }
532 
533  inline internal::SharedAttributableData &get()
534  {
535  return **m_attri;
536  }
537  inline internal::SharedAttributableData const &get() const
538  {
539  return **m_attri;
540  }
541 
542  bool dirty() const
543  {
544  return writable().dirtySelf;
545  }
548  bool dirtyRecursive() const
549  {
550  return writable().dirtyRecursive;
551  }
552  void setDirty(bool dirty_in)
553  {
554  auto &w = writable();
555  w.dirtySelf = dirty_in;
556  setDirtyRecursive(dirty_in);
557  }
558  /* Amortized O(1) if dirty_in is true, else O(1).
559  *
560  * Must be used carefully with `dirty_in == false` since it is assumed that
561  * all children are not dirty.
562  *
563  * Invariant of dirtyRecursive:
564  * this->dirtyRecursive implies parent->dirtyRecursive.
565  *
566  * Hence:
567  *
568  * * If dirty_in is true: This needs only go up far enough until a parent is
569  * found that itself is dirtyRecursive.
570  * * If dirty_in is false: Only sets `this` to `dirtyRecursive == false`.
571  * The caller must ensure that the invariant holds (e.g. clearing
572  * everything during flushing or reading logic).
573  */
574  void setDirtyRecursive(bool dirty_in)
575  {
576  auto &w = writable();
577  w.dirtyRecursive = dirty_in;
578  if (dirty_in)
579  {
580  auto current = w.parent;
581  while (current && !current->dirtyRecursive)
582  {
583  current->dirtyRecursive = true;
584  current = current->parent;
585  }
586  }
587  }
588  bool written() const
589  {
590  return writable().written;
591  }
592  enum class EnqueueAsynchronously : bool
593  {
594  Yes,
595  No
596  };
597  /*
598  * setWritten() will take effect immediately.
599  * But it might additionally be necessary in some situations to enqueue a
600  * SET_WRITTEN task to the backend:
601  * A single flush() operation might encompass different Iterations. In
602  * file-based Iteration encoding, some objects must be written to every
603  * single file, thus their `written` flag must be restored to `false` for
604  * each Iteration. When flushing multiple Iterations at once, this must
605  * happen as an asynchronous IO task.
606  */
607  void setWritten(bool val, EnqueueAsynchronously);
608 
609 private:
615  virtual void linkHierarchy(Writable &w);
616 }; // Attributable
617 
618 // note: we explicitly instantiate Attributable::setAttributeImpl for all T in
619 // Datatype in Attributable.cpp
620 template <typename T>
621 inline bool Attributable::setAttribute(std::string const &key, T value)
622 {
623  auto &attri = get();
624  if (IOHandler() &&
625  IOHandler()->m_seriesStatus == internal::SeriesStatus::Default &&
626  Access::READ_ONLY == IOHandler()->m_frontendAccess)
627  {
628  auxiliary::OutOfRangeMsg const out_of_range_msg(
629  "Attribute", "can not be set (read-only).");
630  error::throwNoSuchAttribute(out_of_range_msg(key));
631  }
632 
633  setDirty(true);
634  auto it = attri.m_attributes.lower_bound(key);
635  if (it != attri.m_attributes.end() &&
636  !attri.m_attributes.key_comp()(key, it->first))
637  {
638  // key already exists in map, just replace the value
639  it->second = Attribute(std::move(value));
640  return true;
641  }
642  else
643  {
644  // emplace a new map element for an unknown key
645  attri.m_attributes.emplace_hint(
646  it, std::make_pair(key, Attribute(std::move(value))));
647  return false;
648  }
649 }
650 
651 inline bool
652 Attributable::setAttribute(std::string const &key, char const value[])
653 {
654  return this->setAttribute(key, std::string(value));
655 }
656 
657 template <typename T>
658 inline T Attributable::readFloatingpoint(std::string const &key) const
659 {
660  static_assert(
661  std::is_floating_point<T>::value,
662  "Type of attribute must be floating point");
663 
664  return getAttribute(key).get<T>();
665 }
666 
667 template <typename T>
668 inline std::vector<T>
669 Attributable::readVectorFloatingpoint(std::string const &key) const
670 {
671  static_assert(
672  std::is_floating_point<T>::value,
673  "Type of attribute must be floating point");
674 
675  return getAttribute(key).get<std::vector<T> >();
676 }
677 } // namespace openPMD
Interface for communicating between logical and physically persistent data.
Definition: AbstractIOHandler.hpp:206
Layer to manage storage of attributes associated with file objects.
Definition: Attributable.hpp:222
std::vector< T > readVectorFloatingpoint(std::string const &key) const
Retrieve a vector of values of a floating point Attributes of user-defined precision with ensured typ...
Definition: Attributable.hpp:669
bool containsAttribute(std::string const &key) const
Check whether am Attribute with a given key exists.
Definition: Attributable.cpp:121
MyPath myPath() const
The path to this object within its containing Series.
Definition: Attributable.cpp:236
ReadMode
Definition: Attributable.hpp:429
@ FullyReread
Remove all attributes that have been read previously and read everything that the backend currently h...
Definition: Attributable.hpp:444
@ IgnoreExisting
Don't read an attribute from the backend if it has been previously read.
Definition: Attributable.hpp:434
@ OverrideExisting
Read all the attributes that the backend has to offer and override if it has been read previously.
Definition: Attributable.hpp:439
std::vector< std::string > attributes() const
List all currently stored Attributes' keys.
Definition: Attributable.cpp:105
Attribute getAttribute(std::string const &key) const
Retrieve value of Attribute stored with provided key.
Definition: Attributable.cpp:75
void seriesFlush(std::string backendConfig="{}")
Flush the corresponding Series object.
Definition: Attributable.cpp:138
bool dirtyRecursive() const
O(1).
Definition: Attributable.hpp:548
T readFloatingpoint(std::string const &key) const
Retrieve the value of a floating point Attribute of user-defined precision with ensured type-safety.
Definition: Attributable.hpp:658
size_t numAttributes() const
Count all currently stored Attributes.
Definition: Attributable.cpp:116
bool deleteAttribute(std::string const &key)
Remove Attribute of provided value both logically and physically.
Definition: Attributable.cpp:85
auto containingIteration() const -> std::pair< std::optional< internal::IterationData const * >, internal::SeriesData const * >
Returns the corresponding Iteration.
Definition: Attributable.cpp:160
bool setAttribute(std::string const &key, T value)
Populate Attribute of provided name with provided value.
Definition: Attributable.hpp:621
Attributable & setComment(std::string const &comment)
Populate Attribute corresponding to a comment with the user-supplied comment.
Definition: Attributable.cpp:132
std::string comment() const
Retrieve a user-supplied comment associated with the object.
Definition: Attributable.cpp:127
void iterationFlush(std::string backendConfig="{}")
Flush the containing Iteration.
Definition: Attributable.cpp:144
void touch()
Sets the object dirty to make internal procedures think it has been modified.
Definition: Attributable.cpp:263
Definition: Attribute.hpp:64
U get() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:105
Base class for any type of record (e.g.
Definition: BaseRecord.hpp:182
Map-like container that enforces openPMD requirements and handles IO.
Definition: Container.hpp:104
Logical compilation of data from one snapshot (e.g.
Definition: Iteration.hpp:146
Implementation for the root level of the openPMD hierarchy.
Definition: Series.hpp:288
Entry point for accessing Snapshots/Iterations.
Definition: Snapshots.hpp:50
Definition: ContainerImpls.hpp:36
Layer to mirror structure of logical data and persistent data in file.
Definition: Writable.hpp:76
bool written
Whether a Writable has been written to the backend.
Definition: Writable.hpp:196
bool dirtyRecursive
Tracks if there are unwritten changes anywhere in the tree whose ancestor this Writable is.
Definition: Writable.hpp:175
bool dirtySelf
Tracks if there are unwritten changes for this specific Writable.
Definition: Writable.hpp:162
Return an error string for read-only access.
Definition: OutOfRangeMsg.hpp:37
Definition: Error.hpp:112
Definition: Attributable.hpp:107
Definition: BaseRecord.hpp:52
Definition: RecordComponent.hpp:63
Definition: Attributable.hpp:62
Writable m_writable
The Writable associated with this Attributable.
Definition: Attributable.hpp:81
A_MAP m_attributes
The attributes defined by this Attributable.
Definition: Attributable.hpp:86
Public definitions of openPMD-api.
Definition: Date.cpp:29
Access
File access mode to use during IO.
Definition: Access.hpp:58
String serialization to describe an Attributable.
Definition: Attributable.hpp:367
std::string filePath() const
Reconstructs a path that can be passed to a Series constructor.
Definition: Attributable.cpp:214
std::string seriesName
e.g., samples/git-samples/
Definition: Attributable.hpp:369
std::string seriesExtension
e.g., dataT
Definition: Attributable.hpp:370
std::vector< std::string > group
e.g., .bp, .h5, .json, ...
Definition: Attributable.hpp:379
std::string openPMDPath() const
Return the path ob the object within the openPMD file.
Definition: Attributable.cpp:219
Definition: Attributable.hpp:251
Definition: RecordComponent.hpp:550
Container Element Creation Policy.
Definition: Container.hpp:52