diff --git a/docs/changelog/vecflat.md b/docs/changelog/vecflat.md new file mode 100644 index 000000000..323119b2a --- /dev/null +++ b/docs/changelog/vecflat.md @@ -0,0 +1,19 @@ +# Added VecFlat class + +`vtkm::VecFlat` is a wrapper around a `Vec`-like class that may be a nested +series of vectors. For example, if you run a gradient operation on a vector +field, you are probably going to get a `Vec` of `Vec`s that looks something +like `vtkm::Vec, 3>`. That is fine, but what if +you want to treat the result simply as a `Vec` of size 9? + +The `VecFlat` wrapper class allows you to do this. Simply place the nested +`Vec` as an argument to `VecFlat` and it will behave as a flat `Vec` class. +(In fact, `VecFlat` is a subclass of `Vec`.) The `VecFlat` class can be +copied to and from the nested `Vec` it is wrapping. + +There is a `vtkm::make_VecFlat` convenience function that takes an object +and returns a `vtkm::VecFlat` wrapped around it. + +`VecFlat` works with any `Vec`-like object as well as scalar values. +However, any type used with `VecFlat` must have `VecTraits` defined and the +number of components must be static (i.e. known at compile time). diff --git a/vtkm/CMakeLists.txt b/vtkm/CMakeLists.txt index 531aafe14..ef6d4b187 100644 --- a/vtkm/CMakeLists.txt +++ b/vtkm/CMakeLists.txt @@ -58,6 +58,7 @@ set(headers VecFromPortalPermute.h VecFromVirtPortal.h VectorAnalysis.h + VecFlat.h VecTraits.h VecVariable.h VirtualObjectBase.h diff --git a/vtkm/VecAxisAlignedPointCoordinates.h b/vtkm/VecAxisAlignedPointCoordinates.h index ea4c08d85..586ffc87f 100644 --- a/vtkm/VecAxisAlignedPointCoordinates.h +++ b/vtkm/VecAxisAlignedPointCoordinates.h @@ -167,7 +167,7 @@ struct VecTraits> template using ReplaceComponentType = vtkm::Vec; template - using ReplaceBaseComponenttype = vtkm::Vec, NUM_COMPONENTS>; + using ReplaceBaseComponentType = vtkm::Vec, NUM_COMPONENTS>; template VTKM_EXEC_CONT static void CopyInto(const VecType& src, vtkm::Vec& dest) @@ -176,6 +176,21 @@ struct VecTraits> } }; +/// Helper function for printing out vectors during testing. +/// +template +inline VTKM_CONT std::ostream& operator<<( + std::ostream& stream, + const vtkm::VecAxisAlignedPointCoordinates& vec) +{ + stream << "["; + for (vtkm::IdComponent component = 0; component < vec.NUM_COMPONENTS - 1; component++) + { + stream << vec[component] << ","; + } + return stream << vec[vec.NUM_COMPONENTS - 1] << "]"; +} + } // namespace vtkm #endif //vtk_m_VecAxisAlignedPointCoordinates_h diff --git a/vtkm/VecFlat.h b/vtkm/VecFlat.h new file mode 100644 index 000000000..8f5101861 --- /dev/null +++ b/vtkm/VecFlat.h @@ -0,0 +1,270 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ +#ifndef vtk_m_VecFlat_h +#define vtk_m_VecFlat_h + +#include +#include +#include +#include + +namespace vtkm +{ + +namespace internal +{ + +template ::HasMultipleComponents> +struct TotalNumComponents; + +template +struct TotalNumComponents +{ + VTKM_STATIC_ASSERT_MSG( + (std::is_same::IsSizeStatic, vtkm::VecTraitsTagSizeStatic>::value), + "vtkm::VecFlat can only be used with Vec types with a static number of components."); + using ComponentType = typename vtkm::VecTraits::ComponentType; + static constexpr vtkm::IdComponent value = + vtkm::VecTraits::NUM_COMPONENTS * TotalNumComponents::value; +}; + +template +struct TotalNumComponents +{ + static constexpr vtkm::IdComponent value = 1; +}; + +namespace detail +{ + +template +VTKM_EXEC_CONT T GetFlatVecComponentImpl(const T& component, + vtkm::IdComponent index, + std::true_type vtkmNotUsed(isBase)) +{ + VTKM_ASSERT(index == 0); + return component; +} + +template +VTKM_EXEC_CONT typename vtkm::VecTraits::BaseComponentType +GetFlatVecComponentImpl(const T& vec, vtkm::IdComponent index, std::false_type vtkmNotUsed(isBase)) +{ + using Traits = vtkm::VecTraits; + using ComponentType = typename Traits::ComponentType; + using BaseComponentType = typename Traits::BaseComponentType; + + constexpr vtkm::IdComponent subSize = TotalNumComponents::value; + return GetFlatVecComponentImpl(Traits::GetComponent(vec, index / subSize), + index % subSize, + typename std::is_same::type{}); +} + +} // namespace detail + +template +VTKM_EXEC_CONT typename vtkm::VecTraits::BaseComponentType GetFlatVecComponent( + const T& vec, + vtkm::IdComponent index) +{ + return detail::GetFlatVecComponentImpl(vec, index, std::false_type{}); +} + +namespace detail +{ + +template +VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(T nestedVec, + vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset) +{ + flatVec[flatOffset] = nestedVec; +} + +template +VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(const vtkm::Vec& nestedVec, + vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset) +{ + for (vtkm::IdComponent nestedIndex = 0; nestedIndex < NNest; ++nestedIndex) + { + flatVec[nestedIndex + flatOffset] = nestedVec[nestedIndex]; + } +} + +template +VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(const NestedVecType& nestedVec, + vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset) +{ + using Traits = vtkm::VecTraits; + using ComponentType = typename Traits::ComponentType; + constexpr vtkm::IdComponent subSize = TotalNumComponents::value; + + vtkm::IdComponent flatIndex = flatOffset; + for (vtkm::IdComponent nestIndex = 0; nestIndex < Traits::NUM_COMPONENTS; ++nestIndex) + { + CopyVecNestedToFlatImpl(Traits::GetComponent(nestedVec, nestIndex), flatVec, flatIndex); + flatIndex += subSize; + } +} + +} // namespace detail + +template +VTKM_EXEC_CONT void CopyVecNestedToFlat(const NestedVecType& nestedVec, vtkm::Vec& flatVec) +{ + detail::CopyVecNestedToFlatImpl(nestedVec, flatVec, 0); +} + +namespace detail +{ + +template +VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset, + T& nestedVec) +{ + nestedVec = flatVec[flatOffset]; +} + +template +VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset, + vtkm::Vec& nestedVec) +{ + for (vtkm::IdComponent nestedIndex = 0; nestedIndex < NNest; ++nestedIndex) + { + nestedVec[nestedIndex] = flatVec[nestedIndex + flatOffset]; + } +} + +template +VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset, + vtkm::Vec& nestedVec) +{ + constexpr vtkm::IdComponent subSize = TotalNumComponents::value; + + vtkm::IdComponent flatIndex = flatOffset; + for (vtkm::IdComponent nestIndex = 0; nestIndex < NNest; ++nestIndex) + { + CopyVecFlatToNestedImpl(flatVec, flatIndex, nestedVec[nestIndex]); + flatIndex += subSize; + } +} + +template +VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec& flatVec, + vtkm::IdComponent flatOffset, + NestedVecType& nestedVec) +{ + using Traits = vtkm::VecTraits; + using ComponentType = typename Traits::ComponentType; + constexpr vtkm::IdComponent subSize = TotalNumComponents::value; + + vtkm::IdComponent flatIndex = flatOffset; + for (vtkm::IdComponent nestIndex = 0; nestIndex < Traits::NUM_COMPONENTS; ++nestIndex) + { + ComponentType component; + CopyVecFlatToNestedImpl(flatVec, flatIndex, component); + Traits::SetComponent(nestedVec, nestIndex, component); + flatIndex += subSize; + } +} + +} // namespace detail + +template +VTKM_EXEC_CONT void CopyVecFlatToNested(const vtkm::Vec& flatVec, NestedVecType& nestedVec) +{ + detail::CopyVecFlatToNestedImpl(flatVec, 0, nestedVec); +} + +} // namespace internal + +namespace detail +{ + +template +using VecFlatSuperclass = vtkm::Vec::BaseComponentType, + vtkm::internal::TotalNumComponents::value>; + +} // namespace detail + +/// \brief Treat a `Vec` or `Vec`-like object as a flat `Vec`. +/// +/// The `VecFlat` template wraps around another object that is a nested `Vec` object +/// (that is, a vector of vectors) and treats it like a flat, 1 dimensional `Vec`. +/// For example, let's say that you have a `Vec` of size 3 holding `Vec`s of size 2. +/// +/// ```cpp +/// void Foo(const vtkm::Vec, 3>& nestedVec) +/// { +/// auto flatVec = vtkm::make_VecFlat(nestedVec); +/// ``` +/// +/// `flatVec` is now of type `vtkm::VecFlat, 3>. +/// `flatVec::NUM_COMPONENTS` is 6 (3 * 2). The `[]` operator takes an index between +/// 0 and 5 and returns a value of type `vtkm::Id`. The indices are explored in +/// depth-first order. So `flatVec[0] == nestedVec[0][0]`, `flatVec[1] == nestedVec[0][1]`, +/// `flatVec[2] == nestedVec[1][0]`, and so on. +/// +/// Note that `flatVec` only works with types that have `VecTraits` defined where +/// the `IsSizeStatic` field is `vtkm::VecTraitsTagSizeStatic` (that is, the `NUM_COMPONENTS` +/// constant is defined). +/// +template +class VecFlat : public detail::VecFlatSuperclass +{ + using Superclass = detail::VecFlatSuperclass; + +public: + using Superclass::Superclass; + VecFlat() = default; + + VTKM_EXEC_CONT VecFlat(const T& src) { *this = src; } + + VTKM_EXEC_CONT VecFlat& operator=(const T& src) + { + internal::CopyVecNestedToFlat(src, *this); + return *this; + } + + VTKM_EXEC_CONT operator T() const + { + T nestedVec; + internal::CopyVecFlatToNested(*this, nestedVec); + return nestedVec; + } +}; + +/// \brief Converts a `Vec`-like object to a `VecFlat`. +/// +template +VTKM_EXEC_CONT vtkm::VecFlat make_VecFlat(const T& vec) +{ + return vtkm::VecFlat(vec); +} + +template +struct TypeTraits> : TypeTraits> +{ +}; + +template +struct VecTraits> : VecTraits> +{ +}; + +} // namespace vtkm + +#endif //vtk_m_VecFlat_h diff --git a/vtkm/testing/CMakeLists.txt b/vtkm/testing/CMakeLists.txt index ef4bd3493..ab20a46f3 100644 --- a/vtkm/testing/CMakeLists.txt +++ b/vtkm/testing/CMakeLists.txt @@ -42,6 +42,7 @@ set(unit_tests UnitTestVecFromPortal.cxx UnitTestVecFromPortalPermute.cxx UnitTestVectorAnalysis.cxx + UnitTestVecFlat.cxx UnitTestVecTraits.cxx UnitTestVecVariable.cxx ) diff --git a/vtkm/testing/UnitTestVecFlat.cxx b/vtkm/testing/UnitTestVecFlat.cxx new file mode 100644 index 000000000..184e37ec0 --- /dev/null +++ b/vtkm/testing/UnitTestVecFlat.cxx @@ -0,0 +1,121 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include + +#include + +#include + +#include + +namespace +{ + +template +void CheckTraits(const T&, vtkm::IdComponent numComponents) +{ + VTKM_TEST_ASSERT((std::is_same::DimensionalityTag, + vtkm::TypeTraitsVectorTag>::value)); + VTKM_TEST_ASSERT(vtkm::VecTraits::NUM_COMPONENTS == numComponents); +} + +void TryBasicVec() +{ + using NestedVecType = vtkm::Vec, 3>; + std::cout << "Trying " << vtkm::cont::TypeToString() << std::endl; + + NestedVecType nestedVec = { { 0, 1 }, { 2, 3 }, { 4, 5 } }; + std::cout << " original: " << nestedVec << std::endl; + + auto flatVec = vtkm::make_VecFlat(nestedVec); + std::cout << " flat: " << flatVec << std::endl; + CheckTraits(flatVec, 6); + VTKM_TEST_ASSERT(decltype(flatVec)::NUM_COMPONENTS == 6); + VTKM_TEST_ASSERT(flatVec[0] == 0); + VTKM_TEST_ASSERT(flatVec[1] == 1); + VTKM_TEST_ASSERT(flatVec[2] == 2); + VTKM_TEST_ASSERT(flatVec[3] == 3); + VTKM_TEST_ASSERT(flatVec[4] == 4); + VTKM_TEST_ASSERT(flatVec[5] == 5); + + flatVec = vtkm::VecFlat{ 5, 4, 3, 2, 1, 0 }; + std::cout << " flat backward: " << flatVec << std::endl; + VTKM_TEST_ASSERT(flatVec[0] == 5); + VTKM_TEST_ASSERT(flatVec[1] == 4); + VTKM_TEST_ASSERT(flatVec[2] == 3); + VTKM_TEST_ASSERT(flatVec[3] == 2); + VTKM_TEST_ASSERT(flatVec[4] == 1); + VTKM_TEST_ASSERT(flatVec[5] == 0); + + nestedVec = flatVec; + std::cout << " nested backward: " << nestedVec << std::endl; + VTKM_TEST_ASSERT(nestedVec[0][0] == 5); + VTKM_TEST_ASSERT(nestedVec[0][1] == 4); + VTKM_TEST_ASSERT(nestedVec[1][0] == 3); + VTKM_TEST_ASSERT(nestedVec[1][1] == 2); + VTKM_TEST_ASSERT(nestedVec[2][0] == 1); + VTKM_TEST_ASSERT(nestedVec[2][1] == 0); +} + +void TryScalar() +{ + using ScalarType = vtkm::Id; + std::cout << "Trying " << vtkm::cont::TypeToString() << std::endl; + + ScalarType scalar = TestValue(0, ScalarType{}); + std::cout << " original: " << scalar << std::endl; + + auto flatVec = vtkm::make_VecFlat(scalar); + std::cout << " flat: " << flatVec << std::endl; + CheckTraits(flatVec, 1); + VTKM_TEST_ASSERT(decltype(flatVec)::NUM_COMPONENTS == 1); + VTKM_TEST_ASSERT(test_equal(flatVec[0], TestValue(0, ScalarType{}))); +} + +void TrySpecialVec() +{ + using NestedVecType = vtkm::Vec, 2>; + std::cout << "Trying " << vtkm::cont::TypeToString() << std::endl; + + NestedVecType nestedVec = { { { 0, 0, 0 }, { 1, 1, 1 } }, { { 1, 1, 1 }, { 1, 1, 1 } } }; + std::cout << " original: " << nestedVec << std::endl; + + auto flatVec = vtkm::make_VecFlat(nestedVec); + std::cout << " flat: " << flatVec << std::endl; + CheckTraits(flatVec, 12); + VTKM_TEST_ASSERT(decltype(flatVec)::NUM_COMPONENTS == 12); + VTKM_TEST_ASSERT(test_equal(flatVec[0], nestedVec[0][0][0])); + VTKM_TEST_ASSERT(test_equal(flatVec[1], nestedVec[0][0][1])); + VTKM_TEST_ASSERT(test_equal(flatVec[2], nestedVec[0][0][2])); + VTKM_TEST_ASSERT(test_equal(flatVec[3], nestedVec[0][1][0])); + VTKM_TEST_ASSERT(test_equal(flatVec[4], nestedVec[0][1][1])); + VTKM_TEST_ASSERT(test_equal(flatVec[5], nestedVec[0][1][2])); + VTKM_TEST_ASSERT(test_equal(flatVec[6], nestedVec[1][0][0])); + VTKM_TEST_ASSERT(test_equal(flatVec[7], nestedVec[1][0][1])); + VTKM_TEST_ASSERT(test_equal(flatVec[8], nestedVec[1][0][2])); + VTKM_TEST_ASSERT(test_equal(flatVec[9], nestedVec[1][1][0])); + VTKM_TEST_ASSERT(test_equal(flatVec[10], nestedVec[1][1][1])); + VTKM_TEST_ASSERT(test_equal(flatVec[11], nestedVec[1][1][2])); +} + +void DoTest() +{ + TryBasicVec(); + TryScalar(); + TrySpecialVec(); +} + +} // anonymous namespace + +int UnitTestVecFlat(int argc, char* argv[]) +{ + return vtkm::testing::Testing::Run(DoTest, argc, argv); +}