Skip to content

Commit be0deb0

Browse files
committed
Add and use Attribute::getOptional<T>()
Useful for backends that might not report back the right numerical type
1 parent 0b4bb53 commit be0deb0

File tree

9 files changed

+193
-52
lines changed

9 files changed

+193
-52
lines changed

include/openPMD/backend/Attribute.hpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <complex>
3030
#include <cstdint>
3131
#include <iterator>
32+
#include <optional>
3233
#include <stdexcept>
3334
#include <string>
3435
#include <type_traits>
@@ -117,6 +118,20 @@ class Attribute
117118
*/
118119
template <typename U>
119120
U get() const;
121+
122+
/** Retrieve a stored specific Attribute and cast if convertible.
123+
* Like Attribute::get<>(), but returns an empty std::optional if no
124+
* conversion is possible instead of throwing an exception.
125+
*
126+
* @note This performs a static_cast and might introduce precision loss if
127+
* requested. Check dtype explicitly beforehand if needed.
128+
*
129+
* @tparam U Type of the object to be casted to.
130+
* @return Copy of the retrieved object, casted to type U.
131+
* An empty std::optional if no conversion is possible.
132+
*/
133+
template <typename U>
134+
std::optional<U> getOptional() const;
120135
};
121136

122137
template <typename T, typename U>
@@ -226,6 +241,110 @@ auto doConvert(T *pv) -> U
226241
}
227242
#endif
228243

244+
template <typename T, typename U>
245+
auto doConvertOptional(T *pv) -> std::optional<U>
246+
{
247+
(void)pv;
248+
if constexpr (std::is_convertible_v<T, U>)
249+
{
250+
return static_cast<U>(*pv);
251+
}
252+
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsVector_v<U>)
253+
{
254+
if constexpr (std::is_convertible_v<
255+
typename T::value_type,
256+
typename U::value_type>)
257+
{
258+
U res{};
259+
res.reserve(pv->size());
260+
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
261+
return res;
262+
}
263+
else
264+
{
265+
return {};
266+
}
267+
}
268+
// conversion cast: array to vector
269+
// if a backend reports a std::array<> for something where
270+
// the frontend expects a vector
271+
else if constexpr (auxiliary::IsArray_v<T> && auxiliary::IsVector_v<U>)
272+
{
273+
if constexpr (std::is_convertible_v<
274+
typename T::value_type,
275+
typename U::value_type>)
276+
{
277+
U res{};
278+
res.reserve(pv->size());
279+
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
280+
return res;
281+
}
282+
else
283+
{
284+
return {};
285+
}
286+
}
287+
// conversion cast: vector to array
288+
// if a backend reports a std::vector<> for something where
289+
// the frontend expects an array
290+
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsArray_v<U>)
291+
{
292+
if constexpr (std::is_convertible_v<
293+
typename T::value_type,
294+
typename U::value_type>)
295+
{
296+
U res{};
297+
if (res.size() != pv->size())
298+
{
299+
throw std::runtime_error(
300+
"getCast: no vector to array conversion possible (wrong "
301+
"requested array size).");
302+
}
303+
for (size_t i = 0; i < res.size(); ++i)
304+
{
305+
res[i] = static_cast<typename U::value_type>((*pv)[i]);
306+
}
307+
return res;
308+
}
309+
else
310+
{
311+
return {};
312+
}
313+
}
314+
// conversion cast: turn a single value into a 1-element vector
315+
else if constexpr (auxiliary::IsVector_v<U>)
316+
{
317+
if constexpr (std::is_convertible_v<T, typename U::value_type>)
318+
{
319+
U res{};
320+
res.reserve(1);
321+
res.push_back(static_cast<typename U::value_type>(*pv));
322+
return res;
323+
}
324+
else
325+
{
326+
return {};
327+
}
328+
}
329+
else
330+
{
331+
return {};
332+
}
333+
#if defined(__INTEL_COMPILER)
334+
/*
335+
* ICPC has trouble with if constexpr, thinking that return statements are
336+
* missing afterwards. Deactivate the warning.
337+
* Note that putting a statement here will not help to fix this since it will
338+
* then complain about unreachable code.
339+
* https://community.intel.com/t5/Intel-C-Compiler/quot-if-constexpr-quot-and-quot-missing-return-statement-quot-in/td-p/1154551
340+
*/
341+
#pragma warning(disable : 1011)
342+
}
343+
#pragma warning(default : 1011)
344+
#else
345+
}
346+
#endif
347+
229348
/** Retrieve a stored specific Attribute and cast if convertible.
230349
*
231350
* @throw std::runtime_error if stored object is not static castable to U.
@@ -251,4 +370,16 @@ U Attribute::get() const
251370
return getCast<U>(Variant::getResource());
252371
}
253372

373+
template <typename U>
374+
std::optional<U> Attribute::getOptional() const
375+
{
376+
auto v = Variant::getResource();
377+
378+
return std::visit(
379+
[](auto &&containedValue) -> U {
380+
using containedType = std::decay_t<decltype(containedValue)>;
381+
return doConvert<containedType, U>(&containedValue);
382+
},
383+
v);
384+
}
254385
} // namespace openPMD

include/openPMD/backend/BaseRecord.hpp

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -317,24 +317,10 @@ inline void BaseRecord<T_elem>::readBase()
317317
aRead.name = "unitDimension";
318318
this->IOHandler()->enqueue(IOTask(this, aRead));
319319
this->IOHandler()->flush(internal::defaultFlushParams);
320-
if (*aRead.dtype == DT::ARR_DBL_7)
321-
this->setAttribute(
322-
"unitDimension",
323-
Attribute(*aRead.resource).template get<std::array<double, 7> >());
324-
else if (*aRead.dtype == DT::VEC_DOUBLE)
325-
{
326-
auto vec =
327-
Attribute(*aRead.resource).template get<std::vector<double> >();
328-
if (vec.size() == 7)
329-
{
330-
std::array<double, 7> arr;
331-
std::copy(vec.begin(), vec.end(), arr.begin());
332-
this->setAttribute("unitDimension", arr);
333-
}
334-
else
335-
throw std::runtime_error(
336-
"Unexpected Attribute datatype for 'unitDimension'");
337-
}
320+
if (auto val =
321+
Attribute(*aRead.resource).getOptional<std::array<double, 7> >();
322+
val.has_value())
323+
this->setAttribute("unitDimension", val.value());
338324
else
339325
throw std::runtime_error(
340326
"Unexpected Attribute datatype for 'unitDimension'");
@@ -344,10 +330,14 @@ inline void BaseRecord<T_elem>::readBase()
344330
this->IOHandler()->flush(internal::defaultFlushParams);
345331
if (*aRead.dtype == DT::FLOAT)
346332
this->setAttribute(
347-
"timeOffset", Attribute(*aRead.resource).template get<float>());
333+
"timeOffset", Attribute(*aRead.resource).get<float>());
348334
else if (*aRead.dtype == DT::DOUBLE)
349335
this->setAttribute(
350-
"timeOffset", Attribute(*aRead.resource).template get<double>());
336+
"timeOffset", Attribute(*aRead.resource).get<double>());
337+
// conversion cast if a backend reports an integer type
338+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
339+
val.has_value())
340+
this->setAttribute("timeOffset", val.value());
351341
else
352342
throw std::runtime_error(
353343
"Unexpected Attribute datatype for 'timeOffset'");

src/Iteration.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ void Iteration::read_impl(std::string const &groupPath)
417417
setDt(Attribute(*aRead.resource).get<double>());
418418
else if (*aRead.dtype == DT::LONG_DOUBLE)
419419
setDt(Attribute(*aRead.resource).get<long double>());
420+
// conversion cast if a backend reports an integer type
421+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
422+
val.has_value())
423+
setDt(val.value());
420424
else
421425
throw std::runtime_error("Unexpected Attribute datatype for 'dt'");
422426

@@ -429,14 +433,19 @@ void Iteration::read_impl(std::string const &groupPath)
429433
setTime(Attribute(*aRead.resource).get<double>());
430434
else if (*aRead.dtype == DT::LONG_DOUBLE)
431435
setTime(Attribute(*aRead.resource).get<long double>());
436+
// conversion cast if a backend reports an integer type
437+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
438+
val.has_value())
439+
setTime(val.value());
432440
else
433441
throw std::runtime_error("Unexpected Attribute datatype for 'time'");
434442

435443
aRead.name = "timeUnitSI";
436444
IOHandler()->enqueue(IOTask(this, aRead));
437445
IOHandler()->flush(internal::defaultFlushParams);
438-
if (*aRead.dtype == DT::DOUBLE)
439-
setTimeUnitSI(Attribute(*aRead.resource).get<double>());
446+
if (auto val = Attribute(*aRead.resource).getOptional<double>();
447+
val.has_value())
448+
setTimeUnitSI(val.value());
440449
else
441450
throw std::runtime_error(
442451
"Unexpected Attribute datatype for 'timeUnitSI'");

src/Mesh.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,25 +337,30 @@ void Mesh::read()
337337
else if (
338338
*aRead.dtype == DT::VEC_LONG_DOUBLE || *aRead.dtype == DT::LONG_DOUBLE)
339339
setGridSpacing(a.get<std::vector<long double> >());
340+
// conversion cast if a backend reports an integer type
341+
else if (auto val = a.getOptional<std::vector<double> >(); val.has_value())
342+
setGridSpacing(val.value());
340343
else
341344
throw std::runtime_error(
342345
"Unexpected Attribute datatype for 'gridSpacing'");
343346

344347
aRead.name = "gridGlobalOffset";
345348
IOHandler()->enqueue(IOTask(this, aRead));
346349
IOHandler()->flush(internal::defaultFlushParams);
347-
if (*aRead.dtype == DT::VEC_DOUBLE || *aRead.dtype == DT::DOUBLE)
348-
setGridGlobalOffset(
349-
Attribute(*aRead.resource).get<std::vector<double> >());
350+
if (auto val =
351+
Attribute(*aRead.resource).getOptional<std::vector<double> >();
352+
val.has_value())
353+
setGridGlobalOffset(val.value());
350354
else
351355
throw std::runtime_error(
352356
"Unexpected Attribute datatype for 'gridGlobalOffset'");
353357

354358
aRead.name = "gridUnitSI";
355359
IOHandler()->enqueue(IOTask(this, aRead));
356360
IOHandler()->flush(internal::defaultFlushParams);
357-
if (*aRead.dtype == DT::DOUBLE)
358-
setGridUnitSI(Attribute(*aRead.resource).get<double>());
361+
if (auto val = Attribute(*aRead.resource).getOptional<double>();
362+
val.has_value())
363+
setGridUnitSI(val.value());
359364
else
360365
throw std::runtime_error(
361366
"Unexpected Attribute datatype for 'gridUnitSI'");

src/RecordComponent.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -367,11 +367,9 @@ void RecordComponent::readBase()
367367
Extent e;
368368

369369
// uint64_t check
370-
Datatype const attrDtype = *aRead.dtype;
371-
if (isSame(attrDtype, determineDatatype<std::vector<uint64_t> >()) ||
372-
isSame(attrDtype, determineDatatype<uint64_t>()))
373-
for (auto const &val : a.get<std::vector<uint64_t> >())
374-
e.push_back(val);
370+
if (auto val = a.getOptional<std::vector<uint64_t> >(); val.has_value())
371+
for (auto const &shape : val.value())
372+
e.push_back(shape);
375373
else
376374
{
377375
std::ostringstream oss;
@@ -389,8 +387,9 @@ void RecordComponent::readBase()
389387
aRead.name = "unitSI";
390388
IOHandler()->enqueue(IOTask(this, aRead));
391389
IOHandler()->flush(internal::defaultFlushParams);
392-
if (*aRead.dtype == DT::DOUBLE)
393-
setUnitSI(Attribute(*aRead.resource).get<double>());
390+
if (auto val = Attribute(*aRead.resource).getOptional<double>();
391+
val.has_value())
392+
setUnitSI(val.value());
394393
else
395394
throw std::runtime_error("Unexpected Attribute datatype for 'unitSI'");
396395

src/Series.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,31 +1221,33 @@ void Series::readGorVBased(bool do_init)
12211221
void Series::readBase()
12221222
{
12231223
auto &series = get();
1224-
using DT = Datatype;
12251224
Parameter<Operation::READ_ATT> aRead;
12261225

12271226
aRead.name = "openPMD";
12281227
IOHandler()->enqueue(IOTask(this, aRead));
12291228
IOHandler()->flush(internal::defaultFlushParams);
1230-
if (*aRead.dtype == DT::STRING)
1231-
setOpenPMD(Attribute(*aRead.resource).get<std::string>());
1229+
if (auto val = Attribute(*aRead.resource).getOptional<std::string>();
1230+
val.has_value())
1231+
setOpenPMD(val.value());
12321232
else
12331233
throw std::runtime_error("Unexpected Attribute datatype for 'openPMD'");
12341234

12351235
aRead.name = "openPMDextension";
12361236
IOHandler()->enqueue(IOTask(this, aRead));
12371237
IOHandler()->flush(internal::defaultFlushParams);
1238-
if (*aRead.dtype == determineDatatype<uint32_t>())
1239-
setOpenPMDextension(Attribute(*aRead.resource).get<uint32_t>());
1238+
if (auto val = Attribute(*aRead.resource).getOptional<uint32_t>();
1239+
val.has_value())
1240+
setOpenPMDextension(val.value());
12401241
else
12411242
throw std::runtime_error(
12421243
"Unexpected Attribute datatype for 'openPMDextension'");
12431244

12441245
aRead.name = "basePath";
12451246
IOHandler()->enqueue(IOTask(this, aRead));
12461247
IOHandler()->flush(internal::defaultFlushParams);
1247-
if (*aRead.dtype == DT::STRING)
1248-
setAttribute("basePath", Attribute(*aRead.resource).get<std::string>());
1248+
if (auto val = Attribute(*aRead.resource).getOptional<std::string>();
1249+
val.has_value())
1250+
setAttribute("basePath", val.value());
12491251
else
12501252
throw std::runtime_error(
12511253
"Unexpected Attribute datatype for 'basePath'");
@@ -1260,13 +1262,14 @@ void Series::readBase()
12601262
aRead.name = "meshesPath";
12611263
IOHandler()->enqueue(IOTask(this, aRead));
12621264
IOHandler()->flush(internal::defaultFlushParams);
1263-
if (*aRead.dtype == DT::STRING)
1265+
if (auto val = Attribute(*aRead.resource).getOptional<std::string>();
1266+
val.has_value())
12641267
{
12651268
/* allow setting the meshes path after completed IO */
12661269
for (auto &it : series.iterations)
12671270
it.second.meshes.written() = false;
12681271

1269-
setMeshesPath(Attribute(*aRead.resource).get<std::string>());
1272+
setMeshesPath(val.value());
12701273

12711274
for (auto &it : series.iterations)
12721275
it.second.meshes.written() = true;
@@ -1284,13 +1287,14 @@ void Series::readBase()
12841287
aRead.name = "particlesPath";
12851288
IOHandler()->enqueue(IOTask(this, aRead));
12861289
IOHandler()->flush(internal::defaultFlushParams);
1287-
if (*aRead.dtype == DT::STRING)
1290+
if (auto val = Attribute(*aRead.resource).getOptional<std::string>();
1291+
val.has_value())
12881292
{
12891293
/* allow setting the meshes path after completed IO */
12901294
for (auto &it : series.iterations)
12911295
it.second.particles.written() = false;
12921296

1293-
setParticlesPath(Attribute(*aRead.resource).get<std::string>());
1297+
setParticlesPath(val.value());
12941298

12951299
for (auto &it : series.iterations)
12961300
it.second.particles.written() = true;

src/backend/MeshRecordComponent.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ void MeshRecordComponent::read()
4343
else if (
4444
*aRead.dtype == DT::VEC_LONG_DOUBLE || *aRead.dtype == DT::LONG_DOUBLE)
4545
setPosition(a.get<std::vector<long double> >());
46+
// conversion cast if a backend reports an integer type
47+
else if (auto val = a.getOptional<std::vector<double> >(); val.has_value())
48+
setPosition(val.value());
4649
else
4750
throw std::runtime_error(
4851
"Unexpected Attribute datatype for 'position'");

src/backend/PatchRecord.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,10 @@ void PatchRecord::read()
6363
IOHandler()->enqueue(IOTask(this, aRead));
6464
IOHandler()->flush(internal::defaultFlushParams);
6565

66-
if (*aRead.dtype == Datatype::ARR_DBL_7 ||
67-
*aRead.dtype == Datatype::VEC_DOUBLE)
68-
this->setAttribute(
69-
"unitDimension",
70-
Attribute(*aRead.resource).template get<std::array<double, 7> >());
66+
if (auto val =
67+
Attribute(*aRead.resource).getOptional<std::array<double, 7> >();
68+
val.has_value())
69+
this->setAttribute("unitDimension", val.value());
7170
else
7271
throw std::runtime_error(
7372
"Unexpected Attribute datatype for 'unitDimension'");

0 commit comments

Comments
 (0)