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/HierarchyVisitor.hpp"
29 #include "openPMD/backend/Writable.hpp"
30 
31 #include <cstddef>
32 #include <map>
33 #include <memory>
34 #include <optional>
35 #include <string>
36 #include <type_traits>
37 #include <vector>
38 
39 // expose private and protected members for invasive testing
40 #ifndef OPENPMD_protected
41 #define OPENPMD_protected protected:
42 #endif
43 
44 namespace openPMD
45 {
46 namespace traits
47 {
48  template <typename T>
49  struct GenerationPolicy;
50 } // namespace traits
51 class AbstractFilePosition;
52 class Attributable;
53 class Iteration;
54 class Series;
55 
56 namespace internal
57 {
58  class IterationData;
59  class SeriesData;
60  struct HomogenizeExtents;
61  struct ConfigAttribute;
62  class ScientificDefaults;
63 
65  {
66  friend class openPMD::Attributable;
67 
68  public:
72  virtual ~SharedAttributableData() = default;
73 
75  operator=(SharedAttributableData const &) = delete;
76  SharedAttributableData &operator=(SharedAttributableData &&) = delete;
77 
78  using A_MAP = std::map<std::string, Attribute>;
85 
89  A_MAP m_attributes;
90  };
91 
92  /*
93  * This is essentially a two-level pointer.
94  *
95  * 1. level: Our public API hands out handles to users that are (shared)
96  * pointers to an internal object (PIMPL).
97  * 2. level: Multiple internal objects might refer to the same item in an
98  * openPMD file, e.g. to the same backend object.
99  * So, the internal object for an Attributable is a shared pointer to the
100  * unique object identifying this item.
101  *
102  * Such sharing occurs in the CustomHierarchy class where multiple
103  * containers refer to the same group in the openPMD hierarchy
104  * (container of groups, of meshes, of particle species, of datasets).
105  * This might also become relevant for links as in HDF5 if we choose to
106  * implement them.
107  */
108 
109  class AttributableData : public std::shared_ptr<SharedAttributableData>
110  {
111  friend class openPMD::Attributable;
112 
113  using SharedData_t = std::shared_ptr<SharedAttributableData>;
114  using A_MAP = SharedData_t::element_type::A_MAP;
115 
116  public:
119  AttributableData(AttributableData const &) = delete;
120  AttributableData(AttributableData &&) = delete;
121  virtual ~AttributableData() = default;
122 
123  AttributableData &operator=(AttributableData const &) = delete;
124  AttributableData &operator=(AttributableData &&) = delete;
125 
126  // Make copies explicit, only to be used under the conditions described
127  // above
128  void cloneFrom(AttributableData const &other);
129 
130  template <typename T>
131  T asInternalCopyOf()
132  {
133  auto *self = dynamic_cast<typename T::Data_t *>(this);
134  if (!self)
135  {
136  if constexpr (std::is_same_v<Series, T>)
137  {
138  throw std::runtime_error(
139  "[Attributable::retrieveSeries] Error when trying to "
140  "retrieve the Series object. Note: An instance of the "
141  "Series object must still exist when flushing. A "
142  "common cause for this error is using a flush call on "
143  "a handle (e.g. `Iteration::seriesFlush()`) when the "
144  "original Series object has already gone out of "
145  "scope.");
146  }
147  else
148  {
149  throw std::runtime_error(
150 
151  "[AttributableData::asInternalCopyOf<T>] Error when "
152  "trying to retrieve a containing object. Note: An "
153  "instance of the Series object must still exist when "
154  "flushing. A common cause for this error is using a "
155  "flush call on a handle (e.g. "
156  "`Iteration::seriesFlush()`) when the original Series "
157  "object has already gone out of scope.");
158  }
159  }
160  T res;
161  res.setData(
162  std::shared_ptr<typename T::Data_t>(self, [](auto const *) {}));
163  return res;
164  }
165 
166  inline auto attributes() -> A_MAP &
167  {
168  return operator*().m_attributes;
169  }
170  [[nodiscard]] inline auto attributes() const -> A_MAP const &
171  {
172  return operator*().m_attributes;
173  }
174  [[nodiscard]] inline auto readAttribute(std::string const &name) const
175  -> Attribute const &
176  {
177  auto const &attr = attributes();
178  if (auto it = attr.find(name); it != attr.end())
179  {
180  return it->second;
181  }
182  else
183  {
184  throw error::ReadError(
185  error::AffectedObject::Attribute,
186  error::Reason::NotFound,
187  std::nullopt,
188  "Not found: '" + name + "'.");
189  }
190  }
191  };
192 
193  template <typename, typename>
194  class BaseRecordData;
195 
196  class RecordComponentData;
197 
198  /*
199  * Internal function to turn a handle into an owning handle that will keep
200  * not only itself, but the entire Series alive. Works by hiding a copy of
201  * the Series into the destructor lambda of the internal shared pointer. The
202  * returned handle is entirely safe to use in just the same ways as a normal
203  * handle, just the surrounding Series needs not be kept alive any more
204  * since it is stored within the handle. By storing the Series in the
205  * handle, not in the actual data, reference cycles are avoided.
206  *
207  * Instantiations for T exist for types RecordComponent,
208  * MeshRecordComponent, Mesh, Record, ParticleSpecies, Iteration.
209  */
210  template <typename T>
211  T &makeOwning(T &self, Series);
212 } // namespace internal
213 
214 namespace debug
215 {
216  void printDirty(Series const &);
217 }
218 
225 {
226  // @todo remove unnecessary friend (wew that sounds bitter)
227  using A_MAP = std::map<std::string, Attribute>;
228  friend Writable *getWritable(Attributable *);
229  template <typename T_elem>
230  friend class BaseRecord;
231  template <typename T_elem>
232  friend class BaseRecordInterface;
233  template <typename, typename>
234  friend class internal::BaseRecordData;
235  template <typename T, typename T_key, typename T_container>
236  friend class Container;
237  template <typename T>
238  friend struct traits::GenerationPolicy;
239  friend class Iteration;
240  friend class Series;
241  friend class Writable;
242  friend class internal::RecordComponentData;
243  friend void debug::printDirty(Series const &);
244  template <typename T>
245  friend T &internal::makeOwning(T &self, Series);
246  friend class StatefulSnapshotsContainer;
247  friend class internal::AttributableData;
248  friend class Snapshots;
249  friend struct internal::HomogenizeExtents;
250  friend struct internal::ConfigAttribute;
251  friend class internal::ScientificDefaults;
252 
253 protected:
254  // tag for internal constructor
255  struct NoInit
256  {};
257 
259  std::shared_ptr<Data_t> m_attri;
260 
261 public:
262  Attributable();
263  Attributable(NoInit) noexcept;
264 
265  virtual ~Attributable() = default;
266 
281  template <typename T>
282  bool setAttribute(std::string const &key, T value);
283  bool setAttribute(std::string const &key, char const value[]);
284  bool setAttribute(std::string const &key, Attribute value);
295  Attribute getAttribute(std::string const &key) const;
296 
303  bool deleteAttribute(std::string const &key);
304 
309  std::vector<std::string> attributes() const;
314  size_t numAttributes() const;
320  bool containsAttribute(std::string const &key) const;
321 
327  std::string comment() const;
334  Attributable &setComment(std::string const &comment);
335 
350  void seriesFlush(std::string backendConfig = "{}");
351 
364  void iterationFlush(std::string backendConfig = "{}");
365 
372  struct MyPath
373  {
374  std::string directory;
375  std::string seriesName;
376  std::string seriesExtension;
385  std::vector<std::string> group;
386  Access access;
387 
389  std::string filePath() const;
391  std::string openPMDPath() const;
392  };
393 
399  MyPath myPath() const;
400 
405  void touch();
406 
421  virtual void visitHierarchy(HierarchyVisitor &visitor, bool recursive);
422 
434  template <typename Lambda>
435  void visitHierarchyFromLambda(Lambda &&lambda, bool recursive);
436 
456  void populateMissingMetadata(bool recursive);
457 
458  [[nodiscard]] OpenpmdStandard openPMDStandard() const;
459 
460  // clang-format off
461 OPENPMD_protected
462  // clang-format on
463 
464  Series retrieveSeries() const;
465 
473  [[nodiscard]] auto containingIteration() const -> std::pair<
474  std::optional<internal::IterationData const *>,
475  internal::SeriesData const *>;
476  auto containingIteration() -> std::
477  pair<std::optional<internal::IterationData *>, internal::SeriesData *>;
480  template <bool flush_entire_series>
481  void seriesFlush_impl(internal::FlushParams const &);
482 
483  void flushAttributes(internal::FlushParams const &);
484 
485  enum ReadMode
486  {
502  };
503  void readAttributes(ReadMode);
504 
525  template <typename T>
526  T readFloatingpoint(std::string const &key) const;
548  template <typename T>
549  std::vector<T> readVectorFloatingpoint(std::string const &key) const;
550 
551  /* views into the resources held by m_writable
552  * purely for convenience so code that uses these does not have to go
553  * through m_writable-> */
554  AbstractIOHandler *IOHandler()
555  {
556  return const_cast<AbstractIOHandler *>(
557  static_cast<Attributable const *>(this)->IOHandler());
558  }
559  AbstractIOHandler const *IOHandler() const
560  {
561  auto &opt = writable().IOHandler;
562  if (!opt || !opt->has_value())
563  {
564  return nullptr;
565  }
566  return &*opt->value();
567  }
568  Writable *&parent()
569  {
570  return writable().parent;
571  }
572  Writable const *parent() const
573  {
574  return writable().parent;
575  }
576  Writable &writable()
577  {
578  return (*m_attri)->m_writable;
579  }
580  Writable const &writable() const
581  {
582  return (*m_attri)->m_writable;
583  }
584 
585  inline void setData(std::shared_ptr<internal::AttributableData> attri)
586  {
587  m_attri = std::move(attri);
588  }
589 
590  inline internal::SharedAttributableData &get()
591  {
592  return **m_attri;
593  }
594  inline internal::SharedAttributableData const &get() const
595  {
596  return **m_attri;
597  }
598 
599  bool dirty() const
600  {
601  return writable().dirtySelf;
602  }
605  bool dirtyRecursive() const
606  {
607  return writable().dirtyRecursive;
608  }
609  void setDirty(bool dirty_in)
610  {
611  auto &w = writable();
612  w.dirtySelf = dirty_in;
613  setDirtyRecursive(dirty_in);
614  }
615  /* Amortized O(1) if dirty_in is true, else O(1).
616  *
617  * Must be used carefully with `dirty_in == false` since it is assumed that
618  * all children are not dirty.
619  *
620  * Invariant of dirtyRecursive:
621  * this->dirtyRecursive implies parent->dirtyRecursive.
622  *
623  * Hence:
624  *
625  * * If dirty_in is true: This needs only go up far enough until a parent is
626  * found that itself is dirtyRecursive.
627  * * If dirty_in is false: Only sets `this` to `dirtyRecursive == false`.
628  * The caller must ensure that the invariant holds (e.g. clearing
629  * everything during flushing or reading logic).
630  */
631  void setDirtyRecursive(bool dirty_in)
632  {
633  auto &w = writable();
634  w.dirtyRecursive = dirty_in;
635  if (dirty_in)
636  {
637  auto current = w.parent;
638  while (current && !current->dirtyRecursive)
639  {
640  current->dirtyRecursive = true;
641  current = current->parent;
642  }
643  }
644  }
645  bool written() const
646  {
647  return writable().written;
648  }
649  enum class EnqueueAsynchronously : uint8_t
650  {
651  OnlyAsync,
652  Both,
653  No
654  };
655  /*
656  * setWritten() will take effect immediately.
657  * But it might additionally be necessary in some situations to enqueue a
658  * SET_WRITTEN task to the backend:
659  * A single flush() operation might encompass different Iterations. In
660  * file-based Iteration encoding, some objects must be written to every
661  * single file, thus their `written` flag must be restored to `false` for
662  * each Iteration. When flushing multiple Iterations at once, this must
663  * happen as an asynchronous IO task.
664  */
665  void setWritten(bool val, EnqueueAsynchronously);
666 
667 private:
673  virtual void linkHierarchy(Writable &w);
674 }; // Attributable
675 
676 // note: we explicitly instantiate Attributable::setAttributeImpl for all T in
677 // Datatype in Attributable.cpp
678 template <typename T>
679 inline bool Attributable::setAttribute(std::string const &key, T value)
680 {
681  auto &attri = get();
682  if (IOHandler() &&
683  IOHandler()->m_seriesStatus == internal::SeriesStatus::Default &&
684  Access::READ_ONLY == IOHandler()->m_frontendAccess)
685  {
686  auxiliary::OutOfRangeMsg const out_of_range_msg(
687  "Attribute", "can not be set (read-only).");
688  error::throwNoSuchAttribute(out_of_range_msg(key));
689  }
690 
691  setDirty(true);
692  auto it = attri.m_attributes.lower_bound(key);
693  if (it != attri.m_attributes.end() &&
694  !attri.m_attributes.key_comp()(key, it->first))
695  {
696  // key already exists in map, just replace the value
697  it->second = Attribute(std::move(value));
698  return true;
699  }
700  else
701  {
702  // emplace a new map element for an unknown key
703  attri.m_attributes.emplace_hint(
704  it, std::make_pair(key, Attribute(std::move(value))));
705  return false;
706  }
707 }
708 
709 inline bool
710 Attributable::setAttribute(std::string const &key, char const value[])
711 {
712  return this->setAttribute(key, std::string(value));
713 }
714 
715 template <typename T>
716 inline T Attributable::readFloatingpoint(std::string const &key) const
717 {
718  static_assert(
719  std::is_floating_point<T>::value,
720  "Type of attribute must be floating point");
721 
722  return getAttribute(key).get<T>();
723 }
724 
725 template <typename T>
726 inline std::vector<T>
727 Attributable::readVectorFloatingpoint(std::string const &key) const
728 {
729  static_assert(
730  std::is_floating_point<T>::value,
731  "Type of attribute must be floating point");
732 
733  return getAttribute(key).get<std::vector<T> >();
734 }
735 } // 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:225
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:727
bool containsAttribute(std::string const &key) const
Check whether am Attribute with a given key exists.
Definition: Attributable.cpp:153
MyPath myPath() const
The path to this object within its containing Series.
Definition: Attributable.cpp:268
void populateMissingMetadata(bool recursive)
Create standard defined attributes with default values now, insofar they are still missing.
Definition: Attributable.cpp:306
ReadMode
Definition: Attributable.hpp:486
@ FullyReread
Remove all attributes that have been read previously and read everything that the backend currently h...
Definition: Attributable.hpp:501
@ IgnoreExisting
Don't read an attribute from the backend if it has been previously read.
Definition: Attributable.hpp:491
@ OverrideExisting
Read all the attributes that the backend has to offer and override if it has been read previously.
Definition: Attributable.hpp:496
std::vector< std::string > attributes() const
List all currently stored Attributes' keys.
Definition: Attributable.cpp:137
Attribute getAttribute(std::string const &key) const
Retrieve value of Attribute stored with provided key.
Definition: Attributable.cpp:107
void seriesFlush(std::string backendConfig="{}")
Flush the corresponding Series object.
Definition: Attributable.cpp:170
bool dirtyRecursive() const
O(1).
Definition: Attributable.hpp:605
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:716
size_t numAttributes() const
Count all currently stored Attributes.
Definition: Attributable.cpp:148
bool deleteAttribute(std::string const &key)
Remove Attribute of provided value both logically and physically.
Definition: Attributable.cpp:117
auto containingIteration() const -> std::pair< std::optional< internal::IterationData const * >, internal::SeriesData const * >
Returns the corresponding Iteration.
Definition: Attributable.cpp:192
bool setAttribute(std::string const &key, T value)
Populate Attribute of provided name with provided value.
Definition: Attributable.hpp:679
Attributable & setComment(std::string const &comment)
Populate Attribute corresponding to a comment with the user-supplied comment.
Definition: Attributable.cpp:164
std::string comment() const
Retrieve a user-supplied comment associated with the object.
Definition: Attributable.cpp:159
void iterationFlush(std::string backendConfig="{}")
Flush the containing Iteration.
Definition: Attributable.cpp:176
void touch()
Sets the object dirty to make internal procedures think it has been modified.
Definition: Attributable.cpp:295
void visitHierarchyFromLambda(Lambda &&lambda, bool recursive)
Visitor pattern for the openPMD object hierarchy in postfix traversal, lambda version.
Definition: HierarchyVisitorImpl.hpp:88
virtual void visitHierarchy(HierarchyVisitor &visitor, bool recursive)
Visitor pattern for the openPMD object hierarchy in postfix traversal.
Definition: Attributable.cpp:300
Definition: Attribute.hpp:64
U get() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:112
Base class for any type of record (e.g.
Definition: BaseRecord.hpp:186
Map-like container that enforces openPMD requirements and handles IO.
Definition: Container.hpp:106
Definition: HierarchyVisitor.hpp:20
Logical compilation of data from one snapshot (e.g.
Definition: Iteration.hpp:172
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:77
bool written
Whether a Writable has been written to the backend.
Definition: Writable.hpp:198
bool dirtyRecursive
Tracks if there are unwritten changes anywhere in the tree whose ancestor this Writable is.
Definition: Writable.hpp:177
bool dirtySelf
Tracks if there are unwritten changes for this specific Writable.
Definition: Writable.hpp:164
Return an error string for read-only access.
Definition: OutOfRangeMsg.hpp:37
Definition: Error.hpp:112
Definition: Attributable.hpp:110
Definition: BaseRecord.hpp:54
Definition: RecordComponent.hpp:65
Definition: ScientificDefaults.hpp:23
Definition: Attributable.hpp:65
Writable m_writable
The Writable associated with this Attributable.
Definition: Attributable.hpp:84
A_MAP m_attributes
The attributes defined by this Attributable.
Definition: Attributable.hpp:89
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:373
std::string filePath() const
Reconstructs a path that can be passed to a Series constructor.
Definition: Attributable.cpp:246
std::string seriesName
e.g., samples/git-samples/
Definition: Attributable.hpp:375
std::string seriesExtension
e.g., dataT
Definition: Attributable.hpp:376
std::vector< std::string > group
e.g., .bp, .h5, .json, ...
Definition: Attributable.hpp:385
std::string openPMDPath() const
Return the path ob the object within the openPMD file.
Definition: Attributable.cpp:251
Definition: Attributable.hpp:256
Definition: ConfigAttribute.hpp:18
Definition: RecordComponent.hpp:562
Container Element Creation Policy.
Definition: Container.hpp:54