Skip to content

Commit c484088

Browse files
Attribute preparsing for variable-encoded files in ReadRandomAccess mode: Support modifiable attributes (#1735)
* Steal ADIOS2PreloadAttributes from the past Totgesagte leben länger Removed in #1310 * WIP: Rewrite preload class for attributes * Use AdiosAttributes struct * Go through AdiosAttributes for attribute reading * WIP: Tie things together * Fixes * Fix path reading * Use parameters from actual IO * CI fixes * Fix old implementation in case we want to keep it * Update documentation * CI fixes: noexcept constructor * Remove old implementation * Cleanup
1 parent dac0a06 commit c484088

File tree

9 files changed

+747
-273
lines changed

9 files changed

+747
-273
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ set(IO_SOURCE
437437
src/IO/JSON/JSONIOHandlerImpl.cpp
438438
src/IO/JSON/JSONFilePosition.cpp
439439
src/IO/ADIOS/ADIOS2IOHandler.cpp
440+
src/IO/ADIOS/ADIOS2PreloadAttributes.cpp
440441
src/IO/ADIOS/ADIOS2File.cpp
441442
src/IO/ADIOS/ADIOS2Auxiliary.cpp
442443
src/IO/InvalidatableFile.cpp)

docs/source/usage/workflow.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ The openPMD-api distinguishes between a number of different access modes:
7777
When using such a backend, the access mode will be coerced automatically to *linear read mode*.
7878
Use of Series::readIterations() is mandatory for access.
7979
4. *Random-access read mode* for a variable-based Series is currently experimental.
80-
There is currently only very restricted support for metadata definitions that change across steps:
80+
Support for metadata definitions that change across steps is (currently) restricted:
8181

82-
1. Modifiable attributes (except ``/data/snapshot``) can currently not be read. Attributes such as ``/data/time`` that naturally change their value across Iterations will hence not be really well-usable; the last Iteration's value will currently leak into all other Iterations.
83-
2. There is no support for datasets that do not exist in all Iterations. The internal Iteration layouts should be homogeneous.
82+
1. There is no support for datasets that do not exist in all Iterations. The internal Iteration layouts should be homogeneous.
8483
If you need this feature, please contact the openPMD-api developers; implementing this is currently not a priority.
8584
Datasets that do not exist in all steps will be skipped at read time (with an error).
86-
3. Datasets with changing extents are supported.
85+
86+
This restriction affects only datasets that contain array data.
87+
Datasets defined exlusively in terms of attributes (especially: constant components) may be defined and read in a subselection of Iterations.
88+
2. Datasets with changing extents are supported under the condition that they do not change their dimensionality.
8789

8890
* **Read/Write mode**: Creates a new Series if not existing, otherwise opens an existing Series for reading and writing.
8991
New datasets and iterations will be inserted as needed.

include/openPMD/IO/ADIOS/ADIOS2File.hpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#pragma once
2222

2323
#include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp"
24+
#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp"
2425
#include "openPMD/IO/AbstractIOHandler.hpp"
2526
#include "openPMD/IO/IOTask.hpp"
2627
#include "openPMD/IO/InvalidatableFile.hpp"
@@ -326,16 +327,9 @@ class ADIOS2File
326327
*/
327328
void drop();
328329

329-
AttributeMap_t const &availableAttributes();
330-
331330
std::vector<std::string>
332331
availableAttributesPrefixed(std::string const &prefix);
333332

334-
/*
335-
* See description below.
336-
*/
337-
void invalidateAttributesMap();
338-
339333
AttributeMap_t const &availableVariables();
340334

341335
std::vector<std::string>
@@ -418,6 +412,15 @@ class ADIOS2File
418412
void setStepSelection(std::optional<size_t>);
419413
[[nodiscard]] std::optional<size_t> stepSelection() const;
420414

415+
[[nodiscard]] detail::AdiosAttributes const &attributes() const
416+
{
417+
return m_attributes;
418+
}
419+
[[nodiscard]] detail::AdiosAttributes &attributes()
420+
{
421+
return m_attributes;
422+
}
423+
421424
private:
422425
ADIOS2IOHandlerImpl *m_impl;
423426
std::optional<adios2::Engine> m_engine; //! ADIOS engine
@@ -444,7 +447,7 @@ class ADIOS2File
444447
* the map that would be returned by a call to
445448
* IO::Available(Attributes|Variables).
446449
*/
447-
std::optional<AttributeMap_t> m_availableAttributes;
450+
AdiosAttributes m_attributes;
448451
std::optional<AttributeMap_t> m_availableVariables;
449452

450453
std::set<Writable *> m_pathsMarkedAsActive;

include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "openPMD/Error.hpp"
2424
#include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp"
2525
#include "openPMD/IO/ADIOS/ADIOS2FilePosition.hpp"
26+
#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp"
2627
#include "openPMD/IO/ADIOS/macros.hpp"
2728
#include "openPMD/IO/AbstractIOHandler.hpp"
2829
#include "openPMD/IO/AbstractIOHandlerImpl.hpp"
@@ -541,9 +542,26 @@ namespace detail
541542

542543
struct AttributeReader
543544
{
545+
struct GetAttribute
546+
{
547+
size_t step;
548+
adios2::IO &IO;
549+
detail::AdiosAttributes const &attributes;
550+
template <typename AdiosType>
551+
auto call(std::string const &name) const
552+
-> detail::AttributeWithShapeAndResource<AdiosType>
553+
{
554+
return attributes.getAttribute<AdiosType>(step, IO, name);
555+
}
556+
};
557+
544558
template <typename T>
545-
static Datatype
546-
call(adios2::IO &IO, std::string name, Attribute::resource &resource);
559+
static Datatype call(
560+
size_t step,
561+
adios2::IO &IO,
562+
std::string name,
563+
Attribute::resource &resource,
564+
detail::AdiosAttributes const &);
547565

548566
template <int n, typename... Params>
549567
static Datatype call(Params &&...);
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/* Copyright 2020-2025 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/config.hpp"
24+
#if openPMD_HAVE_ADIOS2
25+
26+
#include <adios2.h>
27+
#include <map>
28+
#include <optional>
29+
#include <variant>
30+
31+
#include "openPMD/Datatype.hpp"
32+
#include "openPMD/auxiliary/Variant.hpp"
33+
34+
namespace openPMD::detail
35+
{
36+
/**
37+
* @brief Pointer to an attribute's data along with its shape.
38+
*
39+
* @tparam T Underlying attribute data type.
40+
*/
41+
template <typename T>
42+
struct AttributeWithShape
43+
{
44+
size_t len;
45+
T const *data;
46+
};
47+
48+
/**
49+
* Class that is responsible for buffering loaded openPMD attributes from
50+
* ADIOS2.
51+
*
52+
* This is used for reading variable-encoded files in random-access mode.
53+
* Random-access mode in ADIOS2 has the restriction that modifiable attributes
54+
* can only be recovered in normal non-random-access read mode, so we open the
55+
* file first in that mode, store all attribute metadata in this class and only
56+
* then continue in random-access mode.
57+
*
58+
*/
59+
class PreloadAdiosAttributes
60+
{
61+
public:
62+
/**
63+
* Meta information on a buffered attribute.
64+
*/
65+
struct AttributeLocation
66+
{
67+
size_t len;
68+
size_t offset;
69+
Datatype dt;
70+
char *destroy = nullptr;
71+
72+
AttributeLocation() = delete;
73+
AttributeLocation(size_t len, size_t offset, Datatype dt);
74+
75+
AttributeLocation(AttributeLocation const &other) = delete;
76+
AttributeLocation &operator=(AttributeLocation const &other) = delete;
77+
78+
AttributeLocation(AttributeLocation &&other) noexcept;
79+
AttributeLocation &operator=(AttributeLocation &&other) noexcept;
80+
81+
~AttributeLocation();
82+
};
83+
84+
private:
85+
/*
86+
* Allocate one large buffer instead of hundreds of single heap
87+
* allocations.
88+
* This will comply with alignment requirements, since
89+
* std::allocator<char>::allocate() will call the untyped new operator
90+
* ::operator new(std::size_t)
91+
* https://en.cppreference.com/w/cpp/memory/allocator/allocate
92+
*/
93+
std::vector<char> m_rawBuffer;
94+
std::map<std::string, AttributeLocation> m_offsets;
95+
96+
public:
97+
explicit PreloadAdiosAttributes() = default;
98+
PreloadAdiosAttributes(PreloadAdiosAttributes const &other) = delete;
99+
PreloadAdiosAttributes &
100+
operator=(PreloadAdiosAttributes const &other) = delete;
101+
102+
PreloadAdiosAttributes(PreloadAdiosAttributes &&other) = default;
103+
PreloadAdiosAttributes &operator=(PreloadAdiosAttributes &&other) = default;
104+
105+
/**
106+
* @brief Load attributes from the current step into the buffer.
107+
*
108+
* @param IO
109+
*/
110+
void preloadAttributes(adios2::IO &IO);
111+
112+
/**
113+
* @brief Get an attribute that has been buffered previously.
114+
*
115+
* @tparam T The underlying primitive datatype of the attribute.
116+
* Will fail if the type found in ADIOS does not match.
117+
* @param name The full name of the attribute.
118+
* @return Pointer to the buffered attribute along with information on
119+
* the attribute's shape. Valid only until any non-const member
120+
* of PreloadAdiosAttributes is called.
121+
*/
122+
template <typename T>
123+
AttributeWithShape<T> getAttribute(std::string const &name) const;
124+
125+
Datatype attributeType(std::string const &name) const;
126+
127+
std::map<std::string, AttributeLocation> const &availableAttributes() const;
128+
};
129+
130+
template <typename T>
131+
struct AttributeWithShapeAndResource : AttributeWithShape<T>
132+
{
133+
AttributeWithShapeAndResource(AttributeWithShape<T> parent);
134+
AttributeWithShapeAndResource(
135+
size_t len_in,
136+
T const *data_in,
137+
std::optional<std::vector<T>> resource_in);
138+
AttributeWithShapeAndResource(adios2::Attribute<T> attr);
139+
operator bool() const;
140+
141+
private:
142+
/*
143+
* Users should still use the API of AttributeWithShape (parent type), we
144+
* just need somewhere to store the std::vector<T> returned by
145+
* Attribute<T>::Data(). This field will not be used when using preparsing,
146+
* because the data pointer will go right into the preparse buffer.
147+
*/
148+
std::optional<std::vector<T>> resource;
149+
};
150+
151+
struct AdiosAttributes
152+
{
153+
using RandomAccess_t = std::vector<PreloadAdiosAttributes>;
154+
struct StreamAccess_t
155+
{
156+
/*
157+
* These are only buffered for performance reasons.
158+
* IO::AvailableAttributes() returns by value, so we should avoid
159+
* calling it too often. Instead, store the returned value along with
160+
* the step, so we know when we need to update again (i.e. when the
161+
* current step changes).
162+
*/
163+
size_t m_currentStep = 0;
164+
std::optional<std::map<std::string, adios2::Params>> m_attributes;
165+
};
166+
167+
/*
168+
* Variant RandomAcces_t has to be initialized explicitly by
169+
* ADIOS2IOHandlerImpl::readAttributeAllsteps(), so we use StreamAccess_t by
170+
* default.
171+
*/
172+
std::variant<RandomAccess_t, StreamAccess_t> m_data = StreamAccess_t{};
173+
174+
/*
175+
* Needs to be this somewhat ugly API since the AvailableAttributes map has
176+
* a different type depending if we use preparsing or not. If we don't use
177+
* preparsing, we just use the returned std::map<std::string,
178+
* adios2::Params> from IO::AvailableAttributes(). Otherwise, we use the
179+
* std::map<std::string, AttributeLocation> map from the
180+
* PreloadAdiosAttributes class. The functor f will be called either with
181+
* the one or the other, so needs to be written such that the mapped-to type
182+
* does not matter.
183+
*/
184+
template <typename Functor>
185+
auto withAvailableAttributes(size_t step, adios2::IO &IO, Functor &&f)
186+
-> decltype(std::forward<Functor>(f)(
187+
std::declval<std::map<std::string, adios2::Params> &>()))
188+
{
189+
using ret_t = decltype(std::forward<Functor>(f)(
190+
std::declval<std::map<std::string, adios2::Params> &>()));
191+
return std::visit(
192+
auxiliary::overloaded{
193+
[step, &f](RandomAccess_t &ra) -> ret_t {
194+
auto &attribute_data = ra.at(step);
195+
return std::forward<Functor>(f)(
196+
attribute_data.availableAttributes());
197+
},
198+
[step, &f, &IO](StreamAccess_t &sa) -> ret_t {
199+
if (!sa.m_attributes.has_value() ||
200+
sa.m_currentStep != step)
201+
{
202+
sa = StreamAccess_t{step, IO.AvailableAttributes()};
203+
}
204+
return std::forward<Functor>(f)(*sa.m_attributes);
205+
}},
206+
m_data);
207+
}
208+
209+
template <typename T>
210+
AttributeWithShapeAndResource<T>
211+
getAttribute(size_t step, adios2::IO &IO, std::string const &name) const;
212+
};
213+
} // namespace openPMD::detail
214+
215+
#endif // openPMD_HAVE_ADIOS2

0 commit comments

Comments
 (0)