Skip to content

Commit fe7cf1e

Browse files
committed
Implement custom meshes and particle paths
Backwards-compatible with old workflows and files
1 parent 24b89e6 commit fe7cf1e

File tree

5 files changed

+165
-63
lines changed

5 files changed

+165
-63
lines changed

include/openPMD/CustomHierarchy.hpp

+14
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,21 @@ namespace internal
4848
};
4949
struct MeshesParticlesPath
5050
{
51+
/*
52+
* @todo: When reading, maybe compile all regexes into one big regex
53+
* for performance?
54+
*/
5155
std::map<std::string, std::regex> meshesPath;
5256
std::map<std::string, std::regex> particlesPath;
5357

58+
/*
59+
* These values decide which path will be returned upon use of the
60+
* shorthand notation s.iterations[0].meshes or .particles.
61+
*
62+
*/
63+
std::string m_defaultMeshesPath = "meshes";
64+
std::string m_defaultParticlesPath = "particles";
65+
5466
explicit MeshesParticlesPath() = default;
5567
MeshesParticlesPath(
5668
std::vector<std::string> const &meshes,
@@ -77,6 +89,8 @@ namespace internal
7789
Container<RecordComponent> m_embeddedDatasets;
7890
Container<Mesh> m_embeddedMeshes;
7991
Container<ParticleSpecies> m_embeddedParticles;
92+
std::string m_defaultMeshesPath;
93+
std::string m_defaultParticlesPath;
8094
};
8195
} // namespace internal
8296

src/CustomHierarchy.cpp

+145-38
Original file line numberDiff line numberDiff line change
@@ -38,38 +38,80 @@
3838
#include <optional>
3939
#include <regex>
4040
#include <sstream>
41+
#include <tuple>
4142

4243
namespace openPMD
4344
{
4445

4546
namespace
4647
{
48+
template <typename Iterator>
4749
std::string
48-
concatWithSep(std::vector<std::string> const &v, std::string const &sep)
50+
concatWithSep(Iterator &&begin, Iterator const &end, std::string const &sep)
4951
{
50-
switch (v.size())
52+
if (begin == end)
5153
{
52-
case 0:
5354
return "";
54-
case 1:
55-
return *v.begin();
56-
default:
57-
break;
5855
}
5956
std::stringstream res;
60-
auto it = v.begin();
61-
res << *it++;
62-
for (; it != v.end(); ++it)
57+
res << *(begin++);
58+
for (; begin != end; ++begin)
6359
{
64-
res << sep << *it;
60+
res << sep << *begin;
6561
}
6662
return res.str();
6763
}
6864

65+
std::string
66+
concatWithSep(std::vector<std::string> const &v, std::string const &sep)
67+
{
68+
return concatWithSep(v.begin(), v.end(), sep);
69+
}
70+
6971
// Not specifying std::regex_constants::optimize here, only using it where
7072
// it makes sense to.
7173
constexpr std::regex_constants::syntax_option_type regex_flags =
7274
std::regex_constants::egrep;
75+
76+
template <typename OutParam>
77+
void setDefaultMeshesParticlesPath(
78+
std::vector<std::string> const &meshes,
79+
std::vector<std::string> const &particles,
80+
OutParam &writeTarget)
81+
{
82+
std::regex is_default_path_specification("[[:alnum:]_]+/", regex_flags);
83+
constexpr char const *default_default_mesh = "meshes";
84+
constexpr char const *default_default_particle = "particles";
85+
for (auto [vec, defaultPath, default_default] :
86+
{std::make_tuple(
87+
&meshes,
88+
&writeTarget.m_defaultMeshesPath,
89+
default_default_mesh),
90+
std::make_tuple(
91+
&particles,
92+
&writeTarget.m_defaultParticlesPath,
93+
default_default_particle)})
94+
{
95+
bool set_default = true;
96+
/*
97+
* The first eligible path in meshesPath/particlesPath is used as
98+
* the default, "meshes"/"particles" otherwise.
99+
*/
100+
for (auto const &path : *vec)
101+
{
102+
if (std::regex_match(path, is_default_path_specification))
103+
{
104+
*defaultPath = auxiliary::replace_last(path, "/", "");
105+
set_default = false;
106+
break;
107+
}
108+
}
109+
if (set_default)
110+
{
111+
*defaultPath = default_default;
112+
}
113+
}
114+
}
73115
} // namespace
74116

75117
namespace internal
@@ -81,15 +123,43 @@ namespace internal
81123
std::vector<std::string> const &path,
82124
std::string const &name)
83125
{
84-
std::string parentPath =
85-
(path.empty() ? "" : concatWithSep(path, "/")) + "/";
86-
std::string fullPath = path.empty() ? name : parentPath + name;
126+
/*
127+
* /group/meshes/E is a mesh if the meshes path contains:
128+
*
129+
* 1) '/group/meshes/' (absolute path to mesh container)
130+
* 2) '/group/meshes/E' (absolute path to mesh itself)
131+
* 3) 'meshes/' (relative path to mesh container)
132+
*
133+
* The potential fourth option 'E' (relative path to mesh itself)
134+
* is not supported. ("Anything that is named 'E' is a mesh" is not
135+
* really a semantic that we want to explicitly support.)
136+
* '/' is never a valid meshes path.
137+
*
138+
* All this analogously for particles path.
139+
*/
140+
std::vector<std::string> pathsToMatch = {
141+
/* option 2) from above */
142+
"/" + (path.empty() ? "" : concatWithSep(path, "/") + "/") +
143+
name};
144+
if (!path.empty())
145+
{
146+
// option 1) from above
147+
pathsToMatch.emplace_back("/" + concatWithSep(path, "/") + "/");
148+
149+
// option 3 from above
150+
pathsToMatch.emplace_back(*path.rbegin() + "/");
151+
}
87152
return std::any_of(
88153
regexes.begin(),
89154
regexes.end(),
90-
[&parentPath, &fullPath](auto const &regex) {
91-
return std::regex_match(parentPath, regex.second) ||
92-
std::regex_match(fullPath, regex.second);
155+
[&pathsToMatch](auto const &regex) {
156+
return std::any_of(
157+
pathsToMatch.begin(),
158+
pathsToMatch.end(),
159+
[&regex](std::string const &candidate_path) {
160+
return std::regex_match(
161+
candidate_path, regex.second);
162+
});
93163
});
94164
}
95165
} // namespace
@@ -98,9 +168,10 @@ namespace internal
98168
std::vector<std::string> const &meshes,
99169
std::vector<std::string> const &particles)
100170
{
171+
std::regex is_default_path_specification("[[:alnum:]_]+/", regex_flags);
101172
for (auto [deque, vec] :
102-
{std::make_pair(&this->meshesPath, &meshes),
103-
std::make_pair(&this->particlesPath, &particles)})
173+
{std::make_tuple(&this->meshesPath, &meshes),
174+
std::make_tuple(&this->particlesPath, &particles)})
104175
{
105176
std::transform(
106177
vec->begin(),
@@ -113,6 +184,7 @@ namespace internal
113184
str, regex_flags | std::regex_constants::optimize));
114185
});
115186
}
187+
setDefaultMeshesParticlesPath(meshes, particles, *this);
116188
}
117189

118190
ContainedType MeshesParticlesPath::determineType(
@@ -262,10 +334,6 @@ void CustomHierarchy::readParticleSpecies(
262334
}
263335
}
264336

265-
// @todo make this flexible again
266-
constexpr char const *defaultMeshesPath = "meshes";
267-
constexpr char const *defaultParticlesPath = "particles";
268-
269337
void CustomHierarchy::read(internal::MeshesParticlesPath const &mpp)
270338
{
271339
std::vector<std::string> currentPath;
@@ -292,6 +360,8 @@ void CustomHierarchy::read(
292360

293361
std::deque<std::string> constantComponentsPushback;
294362
auto &data = get();
363+
data.m_defaultMeshesPath = mpp.m_defaultMeshesPath;
364+
data.m_defaultParticlesPath = mpp.m_defaultParticlesPath;
295365
EraseStaleMeshes meshesMap(data.m_embeddedMeshes);
296366
EraseStaleParticles particlesMap(data.m_embeddedParticles);
297367
for (auto const &path : *pList.paths)
@@ -405,16 +475,19 @@ void CustomHierarchy::flush_internal(
405475

406476
// No need to do anything in access::readOnly since meshes and particles
407477
// are initialized as aliases for subgroups at parsing time
478+
auto &data = get();
479+
data.m_defaultMeshesPath = mpp.m_defaultMeshesPath;
480+
data.m_defaultParticlesPath = mpp.m_defaultParticlesPath;
408481
if (access::write(IOHandler()->m_frontendAccess))
409482
{
410483
if (!meshes.empty())
411484
{
412-
(*this)[defaultMeshesPath];
485+
(*this)[mpp.m_defaultMeshesPath];
413486
}
414487

415488
if (!particles.empty())
416489
{
417-
(*this)[defaultParticlesPath];
490+
(*this)[mpp.m_defaultParticlesPath];
418491
}
419492

420493
flushAttributes(flushParams);
@@ -432,27 +505,53 @@ void CustomHierarchy::flush_internal(
432505
subpath.flush_internal(flushParams, mpp, currentPath);
433506
currentPath.pop_back();
434507
}
435-
auto &data = get();
436508
for (auto &[name, mesh] : data.m_embeddedMeshes)
437509
{
438510
if (!mpp.isMesh(currentPath, name))
439511
{
440-
std::string fullPath = currentPath.empty()
441-
? name
442-
: concatWithSep(currentPath, "/") + "/" + name;
443-
mpp.meshesPath.emplace(fullPath, std::regex(fullPath, regex_flags));
512+
std::string extend_meshes_path;
513+
if (!currentPath.empty() &&
514+
*currentPath.rbegin() == mpp.m_defaultMeshesPath)
515+
{
516+
extend_meshes_path = *currentPath.rbegin() + "/";
517+
}
518+
else
519+
{
520+
521+
extend_meshes_path = "/" +
522+
(currentPath.empty()
523+
? ""
524+
: concatWithSep(currentPath, "/") + "/") +
525+
name;
526+
}
527+
mpp.meshesPath.emplace(
528+
extend_meshes_path,
529+
std::regex(extend_meshes_path, regex_flags));
444530
}
445531
mesh.flush(name, flushParams);
446532
}
447533
for (auto &[name, particleSpecies] : data.m_embeddedParticles)
448534
{
449535
if (!mpp.isParticle(currentPath, name))
450536
{
451-
std::string fullPath = currentPath.empty()
452-
? name
453-
: concatWithSep(currentPath, "/") + "/" + name;
537+
std::string extend_particles_path;
538+
if (!currentPath.empty() &&
539+
*currentPath.rbegin() == mpp.m_defaultParticlesPath)
540+
{
541+
extend_particles_path = *currentPath.rbegin() + "/";
542+
}
543+
else
544+
{
545+
546+
extend_particles_path = "/" +
547+
(currentPath.empty()
548+
? ""
549+
: concatWithSep(currentPath, "/") + "/") +
550+
name;
551+
}
454552
mpp.particlesPath.emplace(
455-
fullPath, std::regex(fullPath, regex_flags));
553+
extend_particles_path,
554+
std::regex(extend_particles_path, regex_flags));
456555
}
457556
particleSpecies.flush(name, flushParams);
458557
}
@@ -596,10 +695,11 @@ template <typename KeyType>
596695
auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
597696
-> mapped_type &
598697
{
599-
auto &cont = container();
698+
auto &data = get();
699+
auto &cont = data.m_container;
600700
auto find_special_key =
601701
[&cont, &provided_key, this](
602-
char const *special_key,
702+
std::string const &special_key,
603703
auto &alias,
604704
auto &&embeddedAccessor) -> std::optional<mapped_type *> {
605705
if (provided_key == special_key)
@@ -656,8 +756,15 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
656756
return std::nullopt;
657757
}
658758
};
759+
if (data.m_defaultMeshesPath.empty() || data.m_defaultParticlesPath.empty())
760+
{
761+
auto const &series = retrieveSeries();
762+
auto meshes_paths = series.meshesPaths();
763+
auto particles_paths = series.particlesPaths();
764+
setDefaultMeshesParticlesPath(meshes_paths, particles_paths, data);
765+
}
659766
if (auto res = find_special_key(
660-
defaultMeshesPath,
767+
data.m_defaultMeshesPath,
661768
meshes,
662769
[](auto &group) {
663770
return &group.m_customHierarchyData->m_embeddedMeshes;
@@ -667,7 +774,7 @@ auto CustomHierarchy::bracketOperatorImpl(KeyType &&provided_key)
667774
return **res;
668775
}
669776
if (auto res = find_special_key(
670-
defaultParticlesPath,
777+
data.m_defaultParticlesPath,
671778
particles,
672779
[](auto &group) {
673780
return &group.m_customHierarchyData->m_embeddedParticles;

src/Iteration.cpp

+4-24
Original file line numberDiff line numberDiff line change
@@ -427,31 +427,11 @@ void Iteration::read_impl(std::string const &groupPath)
427427
Parameter<Operation::LIST_PATHS> pList;
428428
IOHandler()->enqueue(IOTask(this, pList));
429429
std::string version = s.openPMD();
430-
bool hasMeshes = false;
431-
bool hasParticles = false;
432-
if (version == "1.0.0" || version == "1.0.1")
433-
{
434-
IOHandler()->flush(internal::defaultFlushParams);
435-
hasMeshes = std::count(
436-
pList.paths->begin(),
437-
pList.paths->end(),
438-
auxiliary::replace_last(s.meshesPath(), "/", "")) == 1;
439-
hasParticles =
440-
std::count(
441-
pList.paths->begin(),
442-
pList.paths->end(),
443-
auxiliary::replace_last(s.particlesPath(), "/", "")) == 1;
444-
pList.paths->clear();
445-
}
446-
else
447-
{
448-
hasMeshes = s.containsAttribute("meshesPath");
449-
hasParticles = s.containsAttribute("particlesPath");
450-
}
451430

452-
internal::MeshesParticlesPath mpp(
453-
hasMeshes ? s.meshesPaths() : std::vector<std::string>(),
454-
hasParticles ? s.particlesPaths() : std::vector<std::string>());
431+
// @todo restore compatibility with openPMD 1.0.*:
432+
// hasMeshes <-> meshesPath is defined
433+
434+
internal::MeshesParticlesPath mpp(s.meshesPaths(), s.particlesPaths());
455435
CustomHierarchy::read(std::move(mpp));
456436

457437
#ifdef openPMD_USE_INVASIVE_TESTS

test/CoreTest.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ TEST_CASE("custom_hierarchies", "[core]")
163163
{
164164
std::string filePath = "../samples/custom_hierarchies.json";
165165
Series write(filePath, Access::CREATE);
166+
write.setMeshesPath(".*/meshes");
166167
write.iterations[0];
167168
write.close();
168169

test/SerialIOTest.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2013,7 +2013,7 @@ inline void fileBased_write_test(const std::string &backend)
20132013
REQUIRE(o.iterationFormat() == "serial_fileBased_write%03T");
20142014
REQUIRE(o.openPMD() == "1.1.0");
20152015
REQUIRE(o.openPMDextension() == 1u);
2016-
REQUIRE(o.particlesPath() == "particles");
2016+
REQUIRE(o.particlesPath() == "particles/");
20172017
REQUIRE_FALSE(o.containsAttribute("meshesPath"));
20182018
REQUIRE_THROWS_AS(o.meshesPath(), no_such_attribute_error);
20192019
std::array<double, 7> udim{{1, 0, 0, 0, 0, 0, 0}};

0 commit comments

Comments
 (0)