From 066a4a358e066f300731576d2d0cefe4f67563ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 19 May 2022 13:53:15 +0200 Subject: [PATCH] Add and use Attribute::getOptional() Useful for backends that might not report back the right numerical type --- include/openPMD/backend/Attribute.hpp | 131 +++++++++++++++++++++++++ include/openPMD/backend/BaseRecord.hpp | 30 ++---- src/Iteration.cpp | 13 ++- src/Mesh.cpp | 15 ++- src/RecordComponent.cpp | 13 ++- src/Series.cpp | 26 ++--- src/backend/MeshRecordComponent.cpp | 3 + src/backend/PatchRecord.cpp | 9 +- src/backend/PatchRecordComponent.cpp | 5 +- 9 files changed, 193 insertions(+), 52 deletions(-) diff --git a/include/openPMD/backend/Attribute.hpp b/include/openPMD/backend/Attribute.hpp index 0ac26d9dec..739161f95e 100644 --- a/include/openPMD/backend/Attribute.hpp +++ b/include/openPMD/backend/Attribute.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,20 @@ class Attribute */ template U get() const; + + /** Retrieve a stored specific Attribute and cast if convertible. + * Like Attribute::get<>(), but returns an empty std::optional if no + * conversion is possible instead of throwing an exception. + * + * @note This performs a static_cast and might introduce precision loss if + * requested. Check dtype explicitly beforehand if needed. + * + * @tparam U Type of the object to be casted to. + * @return Copy of the retrieved object, casted to type U. + * An empty std::optional if no conversion is possible. + */ + template + std::optional getOptional() const; }; template @@ -228,6 +243,110 @@ auto doConvert(T *pv) -> U } #endif +template +auto doConvertOptional(T *pv) -> std::optional +{ + (void)pv; + if constexpr (std::is_convertible_v) + { + return static_cast(*pv); + } + else if constexpr (auxiliary::IsVector_v && auxiliary::IsVector_v) + { + if constexpr (std::is_convertible_v< + typename T::value_type, + typename U::value_type>) + { + U res{}; + res.reserve(pv->size()); + std::copy(pv->begin(), pv->end(), std::back_inserter(res)); + return res; + } + else + { + return {}; + } + } + // conversion cast: array to vector + // if a backend reports a std::array<> for something where + // the frontend expects a vector + else if constexpr (auxiliary::IsArray_v && auxiliary::IsVector_v) + { + if constexpr (std::is_convertible_v< + typename T::value_type, + typename U::value_type>) + { + U res{}; + res.reserve(pv->size()); + std::copy(pv->begin(), pv->end(), std::back_inserter(res)); + return res; + } + else + { + return {}; + } + } + // conversion cast: vector to array + // if a backend reports a std::vector<> for something where + // the frontend expects an array + else if constexpr (auxiliary::IsVector_v && auxiliary::IsArray_v) + { + if constexpr (std::is_convertible_v< + typename T::value_type, + typename U::value_type>) + { + U res{}; + if (res.size() != pv->size()) + { + throw std::runtime_error( + "getCast: no vector to array conversion possible (wrong " + "requested array size)."); + } + for (size_t i = 0; i < res.size(); ++i) + { + res[i] = static_cast((*pv)[i]); + } + return res; + } + else + { + return {}; + } + } + // conversion cast: turn a single value into a 1-element vector + else if constexpr (auxiliary::IsVector_v) + { + if constexpr (std::is_convertible_v) + { + U res{}; + res.reserve(1); + res.push_back(static_cast(*pv)); + return res; + } + else + { + return {}; + } + } + else + { + return {}; + } +#if defined(__INTEL_COMPILER) +/* + * ICPC has trouble with if constexpr, thinking that return statements are + * missing afterwards. Deactivate the warning. + * Note that putting a statement here will not help to fix this since it will + * then complain about unreachable code. + * https://community.intel.com/t5/Intel-C-Compiler/quot-if-constexpr-quot-and-quot-missing-return-statement-quot-in/td-p/1154551 + */ +#pragma warning(disable : 1011) +} +#pragma warning(default : 1011) +#else +} +#endif + /** Retrieve a stored specific Attribute and cast if convertible. * * @throw std::runtime_error if stored object is not static castable to U. @@ -253,4 +372,16 @@ U Attribute::get() const return getCast(Variant::getResource()); } +template +std::optional Attribute::getOptional() const +{ + auto v = Variant::getResource(); + + return std::visit( + [](auto &&containedValue) -> U { + using containedType = std::decay_t; + return doConvert(&containedValue); + }, + v); +} } // namespace openPMD diff --git a/include/openPMD/backend/BaseRecord.hpp b/include/openPMD/backend/BaseRecord.hpp index 65ae298da9..b35709b240 100644 --- a/include/openPMD/backend/BaseRecord.hpp +++ b/include/openPMD/backend/BaseRecord.hpp @@ -317,24 +317,10 @@ inline void BaseRecord::readBase() aRead.name = "unitDimension"; this->IOHandler()->enqueue(IOTask(this, aRead)); this->IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::ARR_DBL_7) - this->setAttribute( - "unitDimension", - Attribute(*aRead.resource).template get >()); - else if (*aRead.dtype == DT::VEC_DOUBLE) - { - auto vec = - Attribute(*aRead.resource).template get >(); - if (vec.size() == 7) - { - std::array arr; - std::copy(vec.begin(), vec.end(), arr.begin()); - this->setAttribute("unitDimension", arr); - } - else - throw std::runtime_error( - "Unexpected Attribute datatype for 'unitDimension'"); - } + if (auto val = + Attribute(*aRead.resource).getOptional >(); + val.has_value()) + this->setAttribute("unitDimension", val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'unitDimension'"); @@ -344,10 +330,14 @@ inline void BaseRecord::readBase() this->IOHandler()->flush(internal::defaultFlushParams); if (*aRead.dtype == DT::FLOAT) this->setAttribute( - "timeOffset", Attribute(*aRead.resource).template get()); + "timeOffset", Attribute(*aRead.resource).get()); else if (*aRead.dtype == DT::DOUBLE) this->setAttribute( - "timeOffset", Attribute(*aRead.resource).template get()); + "timeOffset", Attribute(*aRead.resource).get()); + // conversion cast if a backend reports an integer type + else if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + this->setAttribute("timeOffset", val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'timeOffset'"); diff --git a/src/Iteration.cpp b/src/Iteration.cpp index 1cf77ec180..1180c312c0 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -417,6 +417,10 @@ void Iteration::read_impl(std::string const &groupPath) setDt(Attribute(*aRead.resource).get()); else if (*aRead.dtype == DT::LONG_DOUBLE) setDt(Attribute(*aRead.resource).get()); + // conversion cast if a backend reports an integer type + else if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setDt(val.value()); else throw std::runtime_error("Unexpected Attribute datatype for 'dt'"); @@ -429,14 +433,19 @@ void Iteration::read_impl(std::string const &groupPath) setTime(Attribute(*aRead.resource).get()); else if (*aRead.dtype == DT::LONG_DOUBLE) setTime(Attribute(*aRead.resource).get()); + // conversion cast if a backend reports an integer type + else if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setTime(val.value()); else throw std::runtime_error("Unexpected Attribute datatype for 'time'"); aRead.name = "timeUnitSI"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::DOUBLE) - setTimeUnitSI(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setTimeUnitSI(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'timeUnitSI'"); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 6f9b891635..afdbe35376 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -337,6 +337,9 @@ void Mesh::read() else if ( *aRead.dtype == DT::VEC_LONG_DOUBLE || *aRead.dtype == DT::LONG_DOUBLE) setGridSpacing(a.get >()); + // conversion cast if a backend reports an integer type + else if (auto val = a.getOptional >(); val.has_value()) + setGridSpacing(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'gridSpacing'"); @@ -344,9 +347,10 @@ void Mesh::read() aRead.name = "gridGlobalOffset"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::VEC_DOUBLE || *aRead.dtype == DT::DOUBLE) - setGridGlobalOffset( - Attribute(*aRead.resource).get >()); + if (auto val = + Attribute(*aRead.resource).getOptional >(); + val.has_value()) + setGridGlobalOffset(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'gridGlobalOffset'"); @@ -354,8 +358,9 @@ void Mesh::read() aRead.name = "gridUnitSI"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::DOUBLE) - setGridUnitSI(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setGridUnitSI(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'gridUnitSI'"); diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index bfa024a786..83d5ac688e 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -326,11 +326,9 @@ void RecordComponent::readBase() Extent e; // uint64_t check - Datatype const attrDtype = *aRead.dtype; - if (isSame(attrDtype, determineDatatype >()) || - isSame(attrDtype, determineDatatype())) - for (auto const &val : a.get >()) - e.push_back(val); + if (auto val = a.getOptional >(); val.has_value()) + for (auto const &shape : val.value()) + e.push_back(shape); else { std::ostringstream oss; @@ -348,8 +346,9 @@ void RecordComponent::readBase() aRead.name = "unitSI"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::DOUBLE) - setUnitSI(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setUnitSI(val.value()); else throw std::runtime_error("Unexpected Attribute datatype for 'unitSI'"); diff --git a/src/Series.cpp b/src/Series.cpp index 814b0e2489..567e80309a 100644 --- a/src/Series.cpp +++ b/src/Series.cpp @@ -1241,22 +1241,23 @@ void Series::readGorVBased(bool do_init) void Series::readBase() { auto &series = get(); - using DT = Datatype; Parameter aRead; aRead.name = "openPMD"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::STRING) - setOpenPMD(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setOpenPMD(val.value()); else throw std::runtime_error("Unexpected Attribute datatype for 'openPMD'"); aRead.name = "openPMDextension"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == determineDatatype()) - setOpenPMDextension(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setOpenPMDextension(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'openPMDextension'"); @@ -1264,8 +1265,9 @@ void Series::readBase() aRead.name = "basePath"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::STRING) - setAttribute("basePath", Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setAttribute("basePath", val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'basePath'"); @@ -1280,13 +1282,14 @@ void Series::readBase() aRead.name = "meshesPath"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::STRING) + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) { /* allow setting the meshes path after completed IO */ for (auto &it : series.iterations) it.second.meshes.written() = false; - setMeshesPath(Attribute(*aRead.resource).get()); + setMeshesPath(val.value()); for (auto &it : series.iterations) it.second.meshes.written() = true; @@ -1304,13 +1307,14 @@ void Series::readBase() aRead.name = "particlesPath"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == DT::STRING) + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) { /* allow setting the meshes path after completed IO */ for (auto &it : series.iterations) it.second.particles.written() = false; - setParticlesPath(Attribute(*aRead.resource).get()); + setParticlesPath(val.value()); for (auto &it : series.iterations) it.second.particles.written() = true; diff --git a/src/backend/MeshRecordComponent.cpp b/src/backend/MeshRecordComponent.cpp index 9602868a07..49f2a99d64 100644 --- a/src/backend/MeshRecordComponent.cpp +++ b/src/backend/MeshRecordComponent.cpp @@ -43,6 +43,9 @@ void MeshRecordComponent::read() else if ( *aRead.dtype == DT::VEC_LONG_DOUBLE || *aRead.dtype == DT::LONG_DOUBLE) setPosition(a.get >()); + // conversion cast if a backend reports an integer type + else if (auto val = a.getOptional >(); val.has_value()) + setPosition(val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'position'"); diff --git a/src/backend/PatchRecord.cpp b/src/backend/PatchRecord.cpp index f31b135b62..5b9b708311 100644 --- a/src/backend/PatchRecord.cpp +++ b/src/backend/PatchRecord.cpp @@ -63,11 +63,10 @@ void PatchRecord::read() IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == Datatype::ARR_DBL_7 || - *aRead.dtype == Datatype::VEC_DOUBLE) - this->setAttribute( - "unitDimension", - Attribute(*aRead.resource).template get >()); + if (auto val = + Attribute(*aRead.resource).getOptional >(); + val.has_value()) + this->setAttribute("unitDimension", val.value()); else throw std::runtime_error( "Unexpected Attribute datatype for 'unitDimension'"); diff --git a/src/backend/PatchRecordComponent.cpp b/src/backend/PatchRecordComponent.cpp index a5178a9911..4cac01424b 100644 --- a/src/backend/PatchRecordComponent.cpp +++ b/src/backend/PatchRecordComponent.cpp @@ -126,8 +126,9 @@ void PatchRecordComponent::read() aRead.name = "unitSI"; IOHandler()->enqueue(IOTask(this, aRead)); IOHandler()->flush(internal::defaultFlushParams); - if (*aRead.dtype == Datatype::DOUBLE) - setUnitSI(Attribute(*aRead.resource).get()); + if (auto val = Attribute(*aRead.resource).getOptional(); + val.has_value()) + setUnitSI(val.value()); else throw std::runtime_error("Unexpected Attribute datatype for 'unitSI'");