diff --git a/docs/changelog/Variant_AsVirtual_force_cast.md b/docs/changelog/Variant_AsVirtual_force_cast.md new file mode 100644 index 000000000..5b1bc1ba1 --- /dev/null +++ b/docs/changelog/Variant_AsVirtual_force_cast.md @@ -0,0 +1,6 @@ +# VariantArrayHandle::AsVirtual() performs casting + +The AsVirtual method of VariantArrayHandle now works for any arithmetic type, +not just the actual type of the underlying array. This works by inserting an +ArrayHandleCast between the underlying concrete array and the new +ArrayHandleVirtual when needed. diff --git a/vtkm/cont/ArrayHandleCast.h b/vtkm/cont/ArrayHandleCast.h index 05e4a3486..ac33857b0 100644 --- a/vtkm/cont/ArrayHandleCast.h +++ b/vtkm/cont/ArrayHandleCast.h @@ -24,6 +24,13 @@ #include +#include + +#include +#include + +#include + namespace vtkm { namespace cont @@ -62,6 +69,60 @@ public: ArrayHandleCast(const ArrayHandleType& handle) : Superclass(handle) { + this->ValidateTypeCast(); + } + +private: + // Log warnings if type cast is valid but lossy: + template + VTKM_CONT static typename std::enable_if::value>::type + ValidateTypeCast() + { +#ifdef VTKM_ENABLE_LOGGING + using DstValueType = T; + using SrcComp = typename vtkm::BaseComponent::Type; + using DstComp = typename vtkm::BaseComponent::Type; + using SrcLimits = std::numeric_limits; + using DstLimits = std::numeric_limits; + + const vtkm::Range SrcRange{ SrcLimits::min(), SrcLimits::max() }; + const vtkm::Range DstRange{ DstLimits::min(), DstLimits::max() }; + + const bool RangeLoss = (SrcRange.Max > DstRange.Max || SrcRange.Min < DstRange.Min); + const bool PrecLoss = SrcLimits::digits > DstLimits::digits; + + if (RangeLoss && PrecLoss) + { + VTKM_LOG_F(vtkm::cont::LogLevel::Warn, + "VariantArrayHandle::AsVirtual: Casting ComponentType of " + "%s to %s reduces range and precision.", + vtkm::cont::TypeToString().c_str(), + vtkm::cont::TypeToString().c_str()); + } + else if (RangeLoss) + { + VTKM_LOG_F(vtkm::cont::LogLevel::Warn, + "VariantArrayHandle::AsVirtual: Casting ComponentType of " + "%s to %s reduces range.", + vtkm::cont::TypeToString().c_str(), + vtkm::cont::TypeToString().c_str()); + } + else if (PrecLoss) + { + VTKM_LOG_F(vtkm::cont::LogLevel::Warn, + "VariantArrayHandle::AsVirtual: Casting ComponentType of " + "%s to %s reduces precision.", + vtkm::cont::TypeToString().c_str(), + vtkm::cont::TypeToString().c_str()); + } +#endif // Logging + } + + template + VTKM_CONT static typename std::enable_if::value>::type + ValidateTypeCast() + { + //no-op if types match } }; diff --git a/vtkm/cont/VariantArrayHandle.h b/vtkm/cont/VariantArrayHandle.h index 9387287e9..1d39678a3 100644 --- a/vtkm/cont/VariantArrayHandle.h +++ b/vtkm/cont/VariantArrayHandle.h @@ -136,13 +136,22 @@ public: return internal::variant::Cast(this->ArrayContainer.get()); } - /// Returns this array cast to a \c ArrayHandleVirtual of the given type. Throws \c - /// ErrorBadType if the cast does not work. + /// Returns this array cast to a \c ArrayHandleVirtual of the given type. + /// This will perform type conversions as necessary, and will log warnings + /// if the conversion is lossy. /// - template + /// This method internally uses CastAndCall. A custom storage tag list may + /// be specified in the second template parameter, which will be passed to + /// the CastAndCall. + /// + template VTKM_CONT vtkm::cont::ArrayHandleVirtual AsVirtual() const { - return internal::variant::Cast>(this->ArrayContainer.get()); + VTKM_IS_LIST_TAG(StorageTagList); + vtkm::cont::internal::variant::ForceCastToVirtual caster; + vtkm::cont::ArrayHandleVirtual output; + this->CastAndCall(StorageTagList{}, caster, output); + return output; } /// Given a references to an ArrayHandle object, casts this array to the diff --git a/vtkm/cont/internal/VariantArrayHandleContainer.h b/vtkm/cont/internal/VariantArrayHandleContainer.h index 6c9daae33..a8a571a1b 100644 --- a/vtkm/cont/internal/VariantArrayHandleContainer.h +++ b/vtkm/cont/internal/VariantArrayHandleContainer.h @@ -22,15 +22,17 @@ #include +#include #include #include +#include +#include #include +#include #include - - namespace vtkm { namespace cont @@ -221,7 +223,6 @@ struct VTKM_ALWAYS_EXPORT Caster } }; - template VTKM_CONT ArrayHandleType Cast(const VariantArrayHandleContainerBase* container) { //container could be nullptr @@ -231,6 +232,54 @@ VTKM_CONT ArrayHandleType Cast(const VariantArrayHandleContainerBase* container) auto ret = Caster{}(container); return ArrayHandleType(std::move(ret)); } + +struct ForceCastToVirtual +{ + template + VTKM_CONT typename std::enable_if::value>::type + operator()(const vtkm::cont::ArrayHandle& input, + vtkm::cont::ArrayHandleVirtual& output) const + { // ValueTypes match + output = vtkm::cont::make_ArrayHandleVirtual(input); + } + + template + VTKM_CONT typename std::enable_if::value>::type + operator()(const vtkm::cont::ArrayHandle& input, + vtkm::cont::ArrayHandleVirtual& output) const + { // ValueTypes do not match + this->ValidateWidthAndCast(input, output); + } + +private: + template ::NUM_COMPONENTS, + vtkm::IdComponent DSize = vtkm::VecTraits::NUM_COMPONENTS> + VTKM_CONT typename std::enable_if::type ValidateWidthAndCast( + const InputType& input, + vtkm::cont::ArrayHandleVirtual& output) const + { // number of components match + auto casted = vtkm::cont::make_ArrayHandleCast(input); + output = vtkm::cont::make_ArrayHandleVirtual(casted); + } + + template ::NUM_COMPONENTS, + vtkm::IdComponent DSize = vtkm::VecTraits::NUM_COMPONENTS> + VTKM_CONT typename std::enable_if::type ValidateWidthAndCast( + const ArrayHandleBase&, + ArrayHandleBase&) const + { // number of components do not match + std::ostringstream str; + str << "VariantArrayHandle::AsVirtual: Cannot cast from " << vtkm::cont::TypeToString() + << " to " << vtkm::cont::TypeToString() << "; " + "number of components must match exactly."; + throw vtkm::cont::ErrorBadType(str.str()); + } +}; } } } diff --git a/vtkm/cont/testing/UnitTestVariantArrayHandle.cxx b/vtkm/cont/testing/UnitTestVariantArrayHandle.cxx index 2a9dd9d1a..31718e516 100644 --- a/vtkm/cont/testing/UnitTestVariantArrayHandle.cxx +++ b/vtkm/cont/testing/UnitTestVariantArrayHandle.cxx @@ -41,6 +41,7 @@ #include #include +#include #include namespace vtkm @@ -266,11 +267,85 @@ void CheckCastToArrayHandle(const ArrayHandleType& array) ArrayHandleType castArray2 = arrayVariant.Cast(); VTKM_TEST_ASSERT(array == castArray2, "Did not get back same array."); +} - using T = typename ArrayHandleType::ValueType; - vtkm::cont::ArrayHandleVirtual castArray3 = arrayVariant.AsVirtual(); - VTKM_TEST_ASSERT(castArray3.GetNumberOfValues() == castArray2.GetNumberOfValues(), - "Did not get back virtual array handle representation."); +// A vtkm::Vec if NumComps > 1, otherwise a scalar +template +using VecOrScalar = typename std::conditional<(NumComps > 1), vtkm::Vec, T>::type; + +template +void CheckCastToVirtualArrayHandle(const ArrayType& array) +{ + VTKM_IS_ARRAY_HANDLE(ArrayType); + + using ValueType = typename ArrayType::ValueType; + using VTraits = vtkm::VecTraits; + using ComponentType = typename VTraits::ComponentType; + static constexpr vtkm::IdComponent NumComps = VTraits::NUM_COMPONENTS; + + using Storage = typename ArrayType::StorageTag; + using StorageList = vtkm::ListTagAppendUnique; + + using TypeList = vtkm::ListTagAppendUnique; + using VariantArrayType = vtkm::cont::VariantArrayHandleBase; + + VariantArrayType arrayVariant = array; + + { + auto testArray = arrayVariant.template AsVirtual(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + { + auto testArray = + arrayVariant.template AsVirtual, StorageList>(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + { + auto testArray = + arrayVariant.template AsVirtual, StorageList>(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + { + auto testArray = + arrayVariant.template AsVirtual, StorageList>(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + { + auto testArray = + arrayVariant.template AsVirtual, StorageList>(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + { + auto testArray = + arrayVariant.template AsVirtual, StorageList>(); + VTKM_TEST_ASSERT(testArray.GetNumberOfValues() == array.GetNumberOfValues(), + "Did not get back virtual array handle representation."); + } + + bool threw = false; + try + { + arrayVariant.template AsVirtual, StorageList>(); + } + catch (vtkm::cont::ErrorBadType&) + { + // caught expected exception + threw = true; + } + + VTKM_TEST_ASSERT(threw, + "Casting to different vector width did not throw expected " + "ErrorBadType exception."); } template @@ -385,6 +460,13 @@ void TryUnusualTypeAndStorage() } } +template +void TryCastToArrayHandle(const ArrayHandleType& array) +{ + CheckCastToArrayHandle(array); + CheckCastToVirtualArrayHandle(array); +} + void TryCastToArrayHandle() { std::cout << " Normal array handle." << std::endl; @@ -393,42 +475,43 @@ void TryCastToArrayHandle() { buffer[index] = TestValue(index, vtkm::Id()); } + vtkm::cont::ArrayHandle array = vtkm::cont::make_ArrayHandle(buffer, ARRAY_SIZE); - CheckCastToArrayHandle(array); + TryCastToArrayHandle(array); std::cout << " Cast array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::make_ArrayHandleCast(array, vtkm::FloatDefault())); + TryCastToArrayHandle(vtkm::cont::make_ArrayHandleCast(array, vtkm::FloatDefault())); std::cout << " Composite vector array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::make_ArrayHandleCompositeVector(array, array)); + TryCastToArrayHandle(vtkm::cont::make_ArrayHandleCompositeVector(array, array)); std::cout << " Constant array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::make_ArrayHandleConstant(5, ARRAY_SIZE)); + TryCastToArrayHandle(vtkm::cont::make_ArrayHandleConstant(5, ARRAY_SIZE)); std::cout << " Counting array handle." << std::endl; vtkm::cont::ArrayHandleCounting countingArray(ARRAY_SIZE - 1, -1, ARRAY_SIZE); - CheckCastToArrayHandle(countingArray); + TryCastToArrayHandle(countingArray); std::cout << " Group vec array handle" << std::endl; vtkm::cont::ArrayHandleGroupVec, 2> groupVecArray(array); - CheckCastToArrayHandle(groupVecArray); + TryCastToArrayHandle(groupVecArray); std::cout << " Implicit array handle." << std::endl; - CheckCastToArrayHandle( + TryCastToArrayHandle( vtkm::cont::make_ArrayHandleImplicit(TestValueFunctor(), ARRAY_SIZE)); std::cout << " Index array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::ArrayHandleIndex(ARRAY_SIZE)); + TryCastToArrayHandle(vtkm::cont::ArrayHandleIndex(ARRAY_SIZE)); std::cout << " Permutation array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::make_ArrayHandlePermutation(countingArray, array)); + TryCastToArrayHandle(vtkm::cont::make_ArrayHandlePermutation(countingArray, array)); std::cout << " Transform array handle." << std::endl; - CheckCastToArrayHandle( + TryCastToArrayHandle( vtkm::cont::make_ArrayHandleTransform(countingArray, TestValueFunctor())); std::cout << " Uniform point coordinates array handle." << std::endl; - CheckCastToArrayHandle(vtkm::cont::ArrayHandleUniformPointCoordinates(vtkm::Id3(ARRAY_SIZE))); + TryCastToArrayHandle(vtkm::cont::ArrayHandleUniformPointCoordinates(vtkm::Id3(ARRAY_SIZE))); // std::cout << " Zip array handle." << std::endl; // CheckCastToArrayHandle(vtkm::cont::make_ArrayHandleZip(countingArray, array));