Skip to content

Commit 7491a9b

Browse files
Variable encoding in ADIOS2: Support partial datasets in ReadRandomAccess (#1746)
* Move ADIOS2 variables buffer to own struct * Implement preparsing for variables * Failing test * Fixes * Fix rankTable reading * Fix non-ADIOS2 builds * Skip preparsing variables if not needed * Fixes for last commit * Remove restrictions from docs * Add nodiscard... * Some testing, fixes and optimizations * Extend test for group-based encoding * Fix use after free * Licensing header * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9e5f210 commit 7491a9b

File tree

13 files changed

+575
-107
lines changed

13 files changed

+575
-107
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ set(IO_SOURCE
441441
src/IO/JSON/JSONFilePosition.cpp
442442
src/IO/ADIOS/ADIOS2IOHandler.cpp
443443
src/IO/ADIOS/ADIOS2PreloadAttributes.cpp
444+
src/IO/ADIOS/ADIOS2PreloadVariables.cpp
444445
src/IO/ADIOS/ADIOS2File.cpp
445446
src/IO/ADIOS/ADIOS2Auxiliary.cpp
446447
src/IO/InvalidatableFile.cpp)

docs/source/usage/workflow.rst

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,7 @@ 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-
Support for metadata definitions that change across steps is (currently) restricted:
81-
82-
1. There is no support for datasets that do not exist in all Iterations. The internal Iteration layouts should be homogeneous.
83-
If you need this feature, please contact the openPMD-api developers; implementing this is currently not a priority.
84-
Datasets that do not exist in all steps will be skipped at read time (with an error).
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.
80+
In this mode, datasets with changing extents are supported under the condition that they do not change their dimensionality.
8981

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

include/openPMD/IO/ADIOS/ADIOS2File.hpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp"
2424
#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp"
25+
#include "openPMD/IO/ADIOS/ADIOS2PreloadVariables.hpp"
2526
#include "openPMD/IO/AbstractIOHandler.hpp"
2627
#include "openPMD/IO/IOTask.hpp"
2728
#include "openPMD/IO/InvalidatableFile.hpp"
@@ -83,7 +84,8 @@ struct DatasetReader
8384
adios2::IO &IO,
8485
adios2::Engine &engine,
8586
std::string const &fileName,
86-
std::optional<size_t> stepSelection);
87+
std::optional<size_t> stepSelection,
88+
detail::AdiosVariables const &av);
8789

8890
static constexpr char const *errorMsg = "ADIOS2: readDataset()";
8991
};
@@ -335,11 +337,6 @@ class ADIOS2File
335337
std::vector<std::string>
336338
availableVariablesPrefixed(std::string const &prefix);
337339

338-
/*
339-
* See description below.
340-
*/
341-
void invalidateVariablesMap();
342-
343340
void markActive(Writable *);
344341

345342
// bool isActive(std::string const & path);
@@ -420,6 +417,14 @@ class ADIOS2File
420417
{
421418
return m_attributes;
422419
}
420+
[[nodiscard]] detail::AdiosVariables const &variables() const
421+
{
422+
return m_variables;
423+
}
424+
[[nodiscard]] detail::AdiosVariables &variables()
425+
{
426+
return m_variables;
427+
}
423428

424429
private:
425430
ADIOS2IOHandlerImpl *m_impl;
@@ -448,7 +453,7 @@ class ADIOS2File
448453
* IO::Available(Attributes|Variables).
449454
*/
450455
AdiosAttributes m_attributes;
451-
std::optional<AttributeMap_t> m_availableVariables;
456+
AdiosVariables m_variables;
452457

453458
std::set<Writable *> m_pathsMarkedAsActive;
454459

include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp"
2525
#include "openPMD/IO/ADIOS/ADIOS2FilePosition.hpp"
2626
#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp"
27+
#include "openPMD/IO/ADIOS/ADIOS2PreloadVariables.hpp"
2728
#include "openPMD/IO/ADIOS/macros.hpp"
2829
#include "openPMD/IO/AbstractIOHandler.hpp"
2930
#include "openPMD/IO/AbstractIOHandlerImpl.hpp"
@@ -426,6 +427,74 @@ class ADIOS2IOHandlerImpl
426427

427428
void dropFileData(InvalidatableFile const &file);
428429

430+
template <typename T>
431+
static void setStepSelectionForVariable(
432+
adios2::Variable<T> var,
433+
std::string const &varName,
434+
size_t step_selection,
435+
size_t file_steps,
436+
detail::AdiosVariables const &av)
437+
{
438+
auto var_steps = var.Steps();
439+
if (var_steps == 1 && step_selection == 0)
440+
{
441+
// variable has no steps
442+
return;
443+
}
444+
if (file_steps != var_steps)
445+
{
446+
if (!av.m_preparsed.has_value())
447+
{
448+
throw error::ReadError(
449+
error::AffectedObject::Dataset,
450+
error::Reason::UnexpectedContent,
451+
"ADIOS2",
452+
"The opened file contains different data per step, but "
453+
"variable data was not preparsed. ERROR: Variable " +
454+
varName + "' has " + std::to_string(var_steps) +
455+
" step(s), but the file has " +
456+
std::to_string(file_steps) + " step(s).");
457+
}
458+
auto preparsed = av.m_preparsed->m_partialVariables.find(varName);
459+
if (preparsed == av.m_preparsed->m_partialVariables.end())
460+
{
461+
throw error::ReadError(
462+
error::AffectedObject::Dataset,
463+
error::Reason::UnexpectedContent,
464+
"ADIOS2",
465+
"The opened file contains different data per step, but "
466+
"variable data contains no preparsing info on '" +
467+
varName + "'. Has " + std::to_string(var_steps) +
468+
" step(s), but the file has " +
469+
std::to_string(file_steps) + " step(s).");
470+
}
471+
auto step_index = std::find(
472+
preparsed->second.begin(),
473+
preparsed->second.end(),
474+
step_selection);
475+
if (step_index == preparsed->second.end())
476+
{
477+
throw error::ReadError(
478+
error::AffectedObject::Dataset,
479+
error::Reason::UnexpectedContent,
480+
"ADIOS2",
481+
"Tried selecting global step " +
482+
std::to_string(step_selection) + " for variable '" +
483+
varName +
484+
"', but variable is not defined for that step (only "
485+
"for steps " +
486+
auxiliary::vec_as_string(preparsed->second) +
487+
"). Has " + std::to_string(var_steps) +
488+
" step(s), but the file has " +
489+
std::to_string(file_steps) + " step(s).");
490+
}
491+
// We need to replace the (global) step selection with the
492+
// (local) step index
493+
step_selection = step_index - preparsed->second.begin();
494+
}
495+
var.SetStepSelection({step_selection, 1});
496+
}
497+
429498
/*
430499
* Prepare a variable that already exists for an IO
431500
* operation, including:
@@ -439,8 +508,10 @@ class ADIOS2IOHandlerImpl
439508
Offset const &offset,
440509
Extent const &extent,
441510
adios2::IO &IO,
511+
adios2::Engine &engine,
442512
std::string const &varName,
443-
std::optional<size_t> stepSelection)
513+
std::optional<size_t> stepSelection,
514+
detail::AdiosVariables const &av)
444515
{
445516
{
446517
auto requiredType = adios2::GetType<T>();
@@ -469,7 +540,9 @@ class ADIOS2IOHandlerImpl
469540
}
470541
if (stepSelection.has_value())
471542
{
472-
var.SetStepSelection({*stepSelection, 1});
543+
auto file_steps = engine.Steps();
544+
setStepSelectionForVariable(
545+
var, varName, *stepSelection, file_steps, av);
473546
}
474547
// TODO leave this check to ADIOS?
475548
adios2::Dims shape = var.Shape();
@@ -615,7 +688,8 @@ namespace detail
615688
Parameter<Operation::OPEN_DATASET> &parameters,
616689
std::optional<size_t> stepSelection,
617690
std::vector<ADIOS2IOHandlerImpl::ParameterizedOperator> const
618-
&operators);
691+
&operators,
692+
detail::AdiosVariables const &);
619693

620694
static constexpr char const *errorMsg = "ADIOS2: openDataset()";
621695
};
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* Copyright 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/auxiliary/Variant.hpp"
24+
#include "openPMD/config.hpp"
25+
#include <deque>
26+
#include <optional>
27+
#include <variant>
28+
#if openPMD_HAVE_ADIOS2
29+
30+
#include <adios2.h>
31+
#include <map>
32+
33+
namespace openPMD::detail
34+
{
35+
struct AdiosVariables
36+
{
37+
// Buffered map for current step
38+
using AttributeMap_t = std::map<std::string, adios2::Params>;
39+
std::optional<AttributeMap_t> m_availableVariables;
40+
// For which step were the above variables buffered?
41+
size_t currentStep;
42+
// Optimization: If variable definitions do not vary across steps, no need
43+
// to recompute them
44+
bool variables_are_static = false;
45+
46+
// Preparsed step data
47+
struct RandomAccessPreparsed_t
48+
{
49+
// Variable only defined in these steps
50+
std::map<std::string, std::vector<size_t>> m_partialVariables;
51+
52+
AttributeMap_t m_allVariables;
53+
};
54+
std::optional<RandomAccessPreparsed_t> m_preparsed;
55+
56+
/*
57+
* If use_step_selection is false, but preparsed step data is available,
58+
* this means that Advance(stepSelection = null) was executed previously.
59+
* So, we can return m_preparsed->m_allVariables.
60+
*/
61+
auto
62+
availableVariables(size_t step, bool use_step_selection, adios2::IO &IO)
63+
-> AttributeMap_t const &;
64+
};
65+
} // namespace openPMD::detail
66+
67+
#endif // openPMD_HAVE_ADIOS2

src/IO/ADIOS/ADIOS2File.cpp

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,17 @@ void DatasetReader::call(
6161
adios2::IO &IO,
6262
adios2::Engine &engine,
6363
std::string const &fileName,
64-
std::optional<size_t> stepSelection)
64+
std::optional<size_t> stepSelection,
65+
detail::AdiosVariables const &av)
6566
{
6667
adios2::Variable<T> var = impl->verifyDataset<T>(
67-
bp.param.offset, bp.param.extent, IO, bp.name, stepSelection);
68+
bp.param.offset,
69+
bp.param.extent,
70+
IO,
71+
engine,
72+
bp.name,
73+
stepSelection,
74+
av);
6875
if (!var)
6976
{
7077
throw std::runtime_error(
@@ -91,15 +98,18 @@ void WriteDataset::call(ADIOS2File &ba, detail::BufferedPut &bp)
9198
if constexpr (std::is_same_v<ptr_type, std::shared_ptr<void const>>)
9299
{
93100
auto ptr = static_cast<T const *>(arg.get());
101+
auto &engine = ba.getEngine();
94102

95103
adios2::Variable<T> var = ba.m_impl->verifyDataset<T>(
96104
bp.param.offset,
97105
bp.param.extent,
98106
ba.m_IO,
107+
engine,
99108
bp.name,
100-
std::nullopt);
109+
std::nullopt,
110+
ba.variables());
101111

102-
ba.getEngine().Put(var, ptr);
112+
engine.Put(var, ptr);
103113
}
104114
else if constexpr (std::is_same_v<
105115
ptr_type,
@@ -146,7 +156,8 @@ void BufferedGet::run(ADIOS2File &ba)
146156
ba.m_IO,
147157
ba.getEngine(),
148158
ba.m_file,
149-
this->stepSelection);
159+
this->stepSelection,
160+
ba.variables());
150161
}
151162

152163
void BufferedPut::run(ADIOS2File &ba)
@@ -160,13 +171,16 @@ struct RunUniquePtrPut
160171
static void call(BufferedUniquePtrPut &bufferedPut, ADIOS2File &ba)
161172
{
162173
auto ptr = static_cast<T const *>(bufferedPut.data.get());
174+
auto &engine = ba.getEngine();
163175
adios2::Variable<T> var = ba.m_impl->verifyDataset<T>(
164176
bufferedPut.offset,
165177
bufferedPut.extent,
166178
ba.m_IO,
179+
engine,
167180
bufferedPut.name,
168-
std::nullopt);
169-
ba.getEngine().Put(var, ptr);
181+
std::nullopt,
182+
ba.variables());
183+
engine.Put(var, ptr);
170184
}
171185

172186
static constexpr char const *errorMsg = "RunUniquePtrPut";
@@ -1336,7 +1350,6 @@ Be aware of the performance implications described above.)");
13361350
case adios2::StepStatus::OtherError:
13371351
throw std::runtime_error("[ADIOS2] Unexpected step status.");
13381352
}
1339-
invalidateVariablesMap();
13401353
m_pathsMarkedAsActive.clear();
13411354
return res;
13421355
}
@@ -1387,22 +1400,10 @@ ADIOS2File::availableVariablesPrefixed(std::string const &prefix)
13871400
prefix, ADIOS2File::availableVariables());
13881401
}
13891402

1390-
void ADIOS2File::invalidateVariablesMap()
1391-
{
1392-
m_availableVariables = std::optional<AttributeMap_t>();
1393-
}
1394-
13951403
ADIOS2File::AttributeMap_t const &ADIOS2File::availableVariables()
13961404
{
1397-
if (m_availableVariables)
1398-
{
1399-
return m_availableVariables.value();
1400-
}
1401-
else
1402-
{
1403-
m_availableVariables = std::make_optional(m_IO.AvailableVariables());
1404-
return m_availableVariables.value();
1405-
}
1405+
return m_variables.availableVariables(
1406+
currentStep(), useStepSelection, m_IO);
14061407
}
14071408

14081409
void ADIOS2File::markActive(Writable *writable)

0 commit comments

Comments
 (0)