Skip to content

Commit 86a15ba

Browse files
Step-Based iteration layout (#855)
* Step-based iteration layout * Add eager parsing test Datasets wth changing dimensions require re-parsing * Fully re-read an Iteration in stepping mode * Remove %V shorthand to select variable-based layout * Hijack some other tests for variable-based encoding * Refine re-parsing When re-parsing a Series, we must keep old handles valid, but we might need to delete stale map members. * Test that attributes don't occur in the wrong step Test still failing * Specifiy more precisely when to re-read attributes * Fix ADIOS1 bug: Wrong datatype reported for unitDimension * Rename __step__ -> snapshot * Code cleanup and in-code documentation
1 parent 14e332e commit 86a15ba

29 files changed

Lines changed: 640 additions & 155 deletions

include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "openPMD/auxiliary/Option.hpp"
3333
#include "openPMD/backend/Writable.hpp"
3434
#include "openPMD/config.hpp"
35+
#include "openPMD/IterationEncoding.hpp"
3536

3637
#if openPMD_HAVE_ADIOS2
3738
# include <adios2.h>
@@ -224,6 +225,11 @@ class ADIOS2IOHandlerImpl
224225

225226
private:
226227
adios2::ADIOS m_ADIOS;
228+
/*
229+
* If the iteration encoding is variableBased, we default to using the
230+
* 2021_02_09 schema since it allows mutable attributes.
231+
*/
232+
IterationEncoding m_iterationEncoding = IterationEncoding::groupBased;
227233
/**
228234
* The ADIOS2 engine type, to be passed to adios2::IO::SetEngine
229235
*/
@@ -1386,7 +1392,7 @@ friend class ADIOS2IOHandlerImpl;
13861392
}
13871393
catch( ... )
13881394
{
1389-
std::cerr << "[~ADIOS2IOHandler] An error occurred." << std::endl;
1395+
std::cerr << "[~ADIOS2IOHandler] An error occurred." << std::endl;
13901396
}
13911397
}
13921398

include/openPMD/IO/AbstractIOHandlerImpl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ class AbstractIOHandlerImpl
241241
*
242242
* The operation should overwrite existing file positions, even when the Writable was already marked written.
243243
* The path parameters.path may contain multiple levels (e.g. first/second/third/). This path should be relative (i.e. it should not start with a slash "/").
244+
* The number of levels may be zero, i.e. parameters.path may be an empty string.
244245
* The Writables file position should correspond to the complete opened path (i.e. first/second/third/ should be assigned to the Writables file position).
245246
* The Writable should be marked written when the operation completes successfully.
246247
*/

include/openPMD/IO/IOTask.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "openPMD/backend/Attribute.hpp"
2626
#include "openPMD/ChunkInfo.hpp"
2727
#include "openPMD/Dataset.hpp"
28+
#include "openPMD/IterationEncoding.hpp"
2829

2930
#include <memory>
3031
#include <map>
@@ -108,7 +109,8 @@ template<>
108109
struct OPENPMDAPI_EXPORT Parameter< Operation::CREATE_FILE > : public AbstractParameter
109110
{
110111
Parameter() = default;
111-
Parameter(Parameter const & p) : AbstractParameter(), name(p.name) {}
112+
Parameter(Parameter const & p) :
113+
AbstractParameter(), name(p.name), encoding(p.encoding) {}
112114

113115
std::unique_ptr< AbstractParameter >
114116
clone() const override
@@ -118,13 +120,15 @@ struct OPENPMDAPI_EXPORT Parameter< Operation::CREATE_FILE > : public AbstractPa
118120
}
119121

120122
std::string name = "";
123+
IterationEncoding encoding = IterationEncoding::groupBased;
121124
};
122125

123126
template<>
124127
struct OPENPMDAPI_EXPORT Parameter< Operation::OPEN_FILE > : public AbstractParameter
125128
{
126129
Parameter() = default;
127-
Parameter(Parameter const & p) : AbstractParameter(), name(p.name) {}
130+
Parameter(Parameter const & p) :
131+
AbstractParameter(), name(p.name), encoding(p.encoding) {}
128132

129133
std::unique_ptr< AbstractParameter >
130134
clone() const override
@@ -134,6 +138,12 @@ struct OPENPMDAPI_EXPORT Parameter< Operation::OPEN_FILE > : public AbstractPara
134138
}
135139

136140
std::string name = "";
141+
/*
142+
* The backends might need to ensure availability of certain features
143+
* for some iteration encodings, e.g. availability of ADIOS steps for
144+
* variableBased encoding.
145+
*/
146+
IterationEncoding encoding = IterationEncoding::groupBased;
137147
};
138148

139149
template<>

include/openPMD/Iteration.hpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,31 @@ class Iteration : public LegacyAttributable
156156

157157
struct DeferredParseAccess
158158
{
159-
std::string index;
159+
/**
160+
* The group path within /data containing this iteration.
161+
* Example: "1" for iteration 1, "" in variable-based iteration
162+
* encoding.
163+
*/
164+
std::string path;
165+
/**
166+
* The iteration index as accessed by the user in series.iterations[i]
167+
*/
168+
uint64_t iteration = 0;
169+
/**
170+
* If this iteration is part of a Series with file-based layout.
171+
* (Group- and variable-based parsing shares the same code logic.)
172+
*/
160173
bool fileBased = false;
174+
/**
175+
* If fileBased == true, the file name (without file path) of the file
176+
* containing this iteration.
177+
*/
161178
std::string filename;
162179
};
163180

164181
void flushFileBased(std::string const&, uint64_t);
165182
void flushGroupBased(uint64_t);
183+
void flushVariableBased(uint64_t);
166184
void flush();
167185
void deferParseAccess( DeferredParseAccess );
168186
/*
@@ -177,11 +195,15 @@ class Iteration : public LegacyAttributable
177195
* allow for those different control flows.
178196
* Finally, read_impl() is called which contains the common parsing
179197
* logic for an iteration.
198+
*
199+
* reread() reads again an Iteration that has been previously read.
200+
* Calling it on an Iteration not yet parsed is an error.
180201
*
181202
*/
182203
void read();
204+
void reread( std::string const & path );
183205
void readFileBased( std::string filePath, std::string const & groupPath );
184-
void readGroupBased( std::string const & groupPath );
206+
void readGorVBased( std::string const & groupPath );
185207
void read_impl( std::string const & groupPath );
186208

187209
/**

include/openPMD/IterationEncoding.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace openPMD
3131
*/
3232
enum class IterationEncoding
3333
{
34-
fileBased, groupBased
34+
fileBased, groupBased, variableBased
3535
};
3636

3737
std::ostream&

include/openPMD/RecordComponent.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,18 +286,17 @@ class RecordComponent : public BaseRecordComponent
286286
bool dirtyRecursive() const;
287287

288288
protected:
289-
/**
290-
* Make sure to parse a RecordComponent only once.
291-
*/
292-
std::shared_ptr< bool > hasBeenRead = std::make_shared< bool >( false );
289+
293290
/**
294291
* The same std::string that the parent class would pass as parameter to
295292
* RecordComponent::flush().
296293
* This is stored only upon RecordComponent::flush() if
297294
* AbstractIOHandler::flushLevel is set to FlushLevel::SkeletonOnly
298295
* (for use by the Span<T>-based overload of RecordComponent::storeChunk()).
296+
* @todo Merge functionality with ownKeyInParent?
299297
*/
300298
std::shared_ptr< std::string > m_name = std::make_shared< std::string >();
299+
301300
}; // RecordComponent
302301
} // namespace openPMD
303302

include/openPMD/Series.hpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ class SeriesImpl : public AttributableImpl
260260
*/
261261
IterationEncoding iterationEncoding() const;
262262
/** Set the <A HREF="https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#iterations-and-time-series">encoding style</A> for multiple iterations in this series.
263+
* A preview on the <A HREF="https://github.com/openPMD/openPMD-standard/pull/250">openPMD 2.0 variable-based iteration encoding</A> can be activated with this call.
264+
* Making full use of the variable-based iteration encoding requires (1) explicit support by the backend (available only in ADIOS2) and (2) use of the openPMD streaming API.
265+
* In other backends and without the streaming API, only one iteration/snapshot may be written in the variable-based encoding, making this encoding a good choice for single-snapshot data dumps.
263266
*
264267
* @param iterationEncoding Desired <A HREF="https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#iterations-and-time-series">encoding style</A> for multiple iterations in this series.
265268
* @return Reference to modified series.
@@ -342,13 +345,20 @@ class SeriesImpl : public AttributableImpl
342345

343346
std::unique_ptr< ParsedInput > parseInput(std::string);
344347
void init(std::shared_ptr< AbstractIOHandler >, std::unique_ptr< ParsedInput >);
345-
void initDefaults();
348+
void initDefaults( IterationEncoding );
346349
std::future< void > flush_impl(
347350
iterations_iterator begin,
348351
iterations_iterator end,
349352
FlushLevel );
350353
void flushFileBased( iterations_iterator begin, iterations_iterator end );
351-
void flushGroupBased( iterations_iterator begin, iterations_iterator end );
354+
/*
355+
* Group-based and variable-based iteration layouts share a lot of logic
356+
* (realistically, the variable-based iteration layout only throws out
357+
* one layer in the hierarchy).
358+
* As a convention, methods that deal with both layouts are called
359+
* .*GorVBased, short for .*GroupOrVariableBased
360+
*/
361+
void flushGorVBased( iterations_iterator begin, iterations_iterator end );
352362
void flushMeshesPath();
353363
void flushParticlesPath();
354364
void readFileBased( );
@@ -357,11 +367,8 @@ class SeriesImpl : public AttributableImpl
357367
* Note on re-parsing of a Series:
358368
* If init == false, the parsing process will seek for new
359369
* Iterations/Records/Record Components etc.
360-
* Re-parsing of objects that have already been parsed is not implemented
361-
* as of yet. Such a facility will be required upon implementing things such
362-
* as resizable datasets.
363370
*/
364-
void readGroupBased( bool init = true );
371+
void readGorVBased( bool init = true );
365372
void readBase();
366373
std::string iterationFilename( uint64_t i );
367374
void openIteration( uint64_t index, Iteration iteration );

include/openPMD/backend/Attributable.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,24 @@ class AttributableImpl
215215
std::vector< std::string > myPath() const;
216216

217217
void flushAttributes();
218-
void readAttributes();
218+
enum ReadMode {
219+
/**
220+
* Don't read an attribute from the backend if it has been previously
221+
* read.
222+
*/
223+
IgnoreExisting,
224+
/**
225+
* Read all the attributes that the backend has to offer and override
226+
* if it has been read previously.
227+
*/
228+
OverrideExisting,
229+
/**
230+
* Remove all attributes that have been read previously and read
231+
* everything that the backend currently has to offer.
232+
*/
233+
FullyReread
234+
};
235+
void readAttributes( ReadMode );
219236

220237
/** Retrieve the value of a floating point Attribute of user-defined precision with ensured type-safety.
221238
*

include/openPMD/backend/Container.hpp

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
#include "openPMD/backend/Attributable.hpp"
2424

2525
#include <initializer_list>
26-
#include <type_traits>
27-
#include <stdexcept>
2826
#include <map>
27+
#include <set>
28+
#include <stdexcept>
2929
#include <string>
30+
#include <type_traits>
31+
#include <utility>
3032

3133
// expose private and protected members for invasive testing
3234
#ifndef OPENPMD_protected
@@ -106,14 +108,16 @@ class Container : public LegacyAttributable
106108
static_assert(
107109
std::is_base_of< AttributableImpl, T >::value,
108110
"Type of container element must be derived from Writable");
109-
using InternalContainer = T_container;
110111

111112
friend class Iteration;
112113
friend class ParticleSpecies;
113114
friend class internal::SeriesData;
114115
friend class SeriesImpl;
115116
friend class Series;
116117

118+
protected:
119+
using InternalContainer = T_container;
120+
117121
public:
118122
using key_type = typename InternalContainer::key_type;
119123
using mapped_type = typename InternalContainer::mapped_type;
@@ -321,6 +325,72 @@ class Container : public LegacyAttributable
321325
}
322326

323327
std::shared_ptr< InternalContainer > m_container;
328+
329+
/**
330+
* This class wraps a Container and forwards operator[]() and at() to it.
331+
* It remembers the keys used for accessing. Upon going out of scope, all
332+
* keys not yet accessed are removed from the Container.
333+
* Note that the container is stored by non-owning reference, thus
334+
* requiring that the original Container stay in scope while using this
335+
* class.
336+
*/
337+
class EraseStaleEntries
338+
{
339+
std::set< key_type > m_accessedKeys;
340+
/*
341+
* Note: Putting a copy here leads to weird bugs due to destructors
342+
* being called too eagerly upon destruction.
343+
* Should be avoidable by extending the frontend redesign to the
344+
* Container class template
345+
* (https://github.com/openPMD/openPMD-api/pull/886)
346+
*/
347+
Container & m_originalContainer;
348+
349+
public:
350+
explicit EraseStaleEntries( Container & container_in )
351+
: m_originalContainer( container_in )
352+
{
353+
}
354+
355+
template< typename K >
356+
mapped_type & operator[]( K && k )
357+
{
358+
m_accessedKeys.insert( k ); // copy
359+
return m_originalContainer[ std::forward< K >( k ) ];
360+
}
361+
362+
template< typename K >
363+
mapped_type & at( K && k )
364+
{
365+
m_accessedKeys.insert( k ); // copy
366+
return m_originalContainer.at( std::forward< K >( k ) );
367+
}
368+
369+
~EraseStaleEntries()
370+
{
371+
auto & map = *m_originalContainer.m_container;
372+
using iterator_t = typename InternalContainer::const_iterator;
373+
std::vector< iterator_t > deleteMe;
374+
deleteMe.reserve( map.size() - m_accessedKeys.size() );
375+
for( iterator_t it = map.begin(); it != map.end(); ++it )
376+
{
377+
auto lookup = m_accessedKeys.find( it->first );
378+
if( lookup == m_accessedKeys.end() )
379+
{
380+
deleteMe.push_back( it );
381+
}
382+
}
383+
for( auto & it : deleteMe )
384+
{
385+
map.erase( it );
386+
}
387+
}
388+
};
389+
390+
EraseStaleEntries eraseStaleEntries()
391+
{
392+
return EraseStaleEntries( *this );
393+
}
324394
};
325395

326396
} // openPMD

include/openPMD/backend/PatchRecordComponent.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class PatchRecordComponent : public BaseRecordComponent
7575
void read();
7676

7777
std::shared_ptr< std::queue< IOTask > > m_chunks;
78-
std::shared_ptr< bool > hasBeenRead = std::make_shared< bool >( false );
7978

8079
/**
8180
* @brief Check recursively whether this RecordComponent is dirty.

0 commit comments

Comments
 (0)