Add 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<vtkm::Vec<vtkm::Float32, 3>, 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.
This commit is contained in:
Kenneth Moreland 2020-09-10 13:07:04 -06:00
parent 73af6e6795
commit 0ab3edd87d
6 changed files with 428 additions and 1 deletions

19
docs/changelog/vecflat.md Normal file

@ -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<vtkm::Vec<vtkm::Float32, 3>, 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).

@ -58,6 +58,7 @@ set(headers
VecFromPortalPermute.h
VecFromVirtPortal.h
VectorAnalysis.h
VecFlat.h
VecTraits.h
VecVariable.h
VirtualObjectBase.h

@ -167,7 +167,7 @@ struct VecTraits<vtkm::VecAxisAlignedPointCoordinates<NumDimensions>>
template <typename NewComponentType>
using ReplaceComponentType = vtkm::Vec<NewComponentType, NUM_COMPONENTS>;
template <typename NewComponentType>
using ReplaceBaseComponenttype = vtkm::Vec<vtkm::Vec<NewComponentType, 3>, NUM_COMPONENTS>;
using ReplaceBaseComponentType = vtkm::Vec<vtkm::Vec<NewComponentType, 3>, NUM_COMPONENTS>;
template <vtkm::IdComponent destSize>
VTKM_EXEC_CONT static void CopyInto(const VecType& src, vtkm::Vec<ComponentType, destSize>& dest)
@ -176,6 +176,21 @@ struct VecTraits<vtkm::VecAxisAlignedPointCoordinates<NumDimensions>>
}
};
/// Helper function for printing out vectors during testing.
///
template <vtkm::IdComponent NumDimensions>
inline VTKM_CONT std::ostream& operator<<(
std::ostream& stream,
const vtkm::VecAxisAlignedPointCoordinates<NumDimensions>& 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

270
vtkm/VecFlat.h Normal file

@ -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 <vtkm/StaticAssert.h>
#include <vtkm/TypeTraits.h>
#include <vtkm/Types.h>
#include <vtkm/VecTraits.h>
namespace vtkm
{
namespace internal
{
template <typename T,
typename MultipleComponents = typename vtkm::VecTraits<T>::HasMultipleComponents>
struct TotalNumComponents;
template <typename T>
struct TotalNumComponents<T, vtkm::VecTraitsTagMultipleComponents>
{
VTKM_STATIC_ASSERT_MSG(
(std::is_same<typename vtkm::VecTraits<T>::IsSizeStatic, vtkm::VecTraitsTagSizeStatic>::value),
"vtkm::VecFlat can only be used with Vec types with a static number of components.");
using ComponentType = typename vtkm::VecTraits<T>::ComponentType;
static constexpr vtkm::IdComponent value =
vtkm::VecTraits<T>::NUM_COMPONENTS * TotalNumComponents<ComponentType>::value;
};
template <typename T>
struct TotalNumComponents<T, vtkm::VecTraitsTagSingleComponent>
{
static constexpr vtkm::IdComponent value = 1;
};
namespace detail
{
template <typename T>
VTKM_EXEC_CONT T GetFlatVecComponentImpl(const T& component,
vtkm::IdComponent index,
std::true_type vtkmNotUsed(isBase))
{
VTKM_ASSERT(index == 0);
return component;
}
template <typename T>
VTKM_EXEC_CONT typename vtkm::VecTraits<T>::BaseComponentType
GetFlatVecComponentImpl(const T& vec, vtkm::IdComponent index, std::false_type vtkmNotUsed(isBase))
{
using Traits = vtkm::VecTraits<T>;
using ComponentType = typename Traits::ComponentType;
using BaseComponentType = typename Traits::BaseComponentType;
constexpr vtkm::IdComponent subSize = TotalNumComponents<ComponentType>::value;
return GetFlatVecComponentImpl(Traits::GetComponent(vec, index / subSize),
index % subSize,
typename std::is_same<ComponentType, BaseComponentType>::type{});
}
} // namespace detail
template <typename T>
VTKM_EXEC_CONT typename vtkm::VecTraits<T>::BaseComponentType GetFlatVecComponent(
const T& vec,
vtkm::IdComponent index)
{
return detail::GetFlatVecComponentImpl(vec, index, std::false_type{});
}
namespace detail
{
template <typename T, vtkm::IdComponent N>
VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(T nestedVec,
vtkm::Vec<T, N>& flatVec,
vtkm::IdComponent flatOffset)
{
flatVec[flatOffset] = nestedVec;
}
template <typename T, vtkm::IdComponent NFlat, vtkm::IdComponent NNest>
VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(const vtkm::Vec<T, NNest>& nestedVec,
vtkm::Vec<T, NFlat>& flatVec,
vtkm::IdComponent flatOffset)
{
for (vtkm::IdComponent nestedIndex = 0; nestedIndex < NNest; ++nestedIndex)
{
flatVec[nestedIndex + flatOffset] = nestedVec[nestedIndex];
}
}
template <typename T, vtkm::IdComponent N, typename NestedVecType>
VTKM_EXEC_CONT void CopyVecNestedToFlatImpl(const NestedVecType& nestedVec,
vtkm::Vec<T, N>& flatVec,
vtkm::IdComponent flatOffset)
{
using Traits = vtkm::VecTraits<NestedVecType>;
using ComponentType = typename Traits::ComponentType;
constexpr vtkm::IdComponent subSize = TotalNumComponents<ComponentType>::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 <typename T, vtkm::IdComponent N, typename NestedVecType>
VTKM_EXEC_CONT void CopyVecNestedToFlat(const NestedVecType& nestedVec, vtkm::Vec<T, N>& flatVec)
{
detail::CopyVecNestedToFlatImpl(nestedVec, flatVec, 0);
}
namespace detail
{
template <typename T, vtkm::IdComponent N>
VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec<T, N>& flatVec,
vtkm::IdComponent flatOffset,
T& nestedVec)
{
nestedVec = flatVec[flatOffset];
}
template <typename T, vtkm::IdComponent NFlat, vtkm::IdComponent NNest>
VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec<T, NFlat>& flatVec,
vtkm::IdComponent flatOffset,
vtkm::Vec<T, NNest>& nestedVec)
{
for (vtkm::IdComponent nestedIndex = 0; nestedIndex < NNest; ++nestedIndex)
{
nestedVec[nestedIndex] = flatVec[nestedIndex + flatOffset];
}
}
template <typename T, vtkm::IdComponent NFlat, typename ComponentType, vtkm::IdComponent NNest>
VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec<T, NFlat>& flatVec,
vtkm::IdComponent flatOffset,
vtkm::Vec<ComponentType, NNest>& nestedVec)
{
constexpr vtkm::IdComponent subSize = TotalNumComponents<ComponentType>::value;
vtkm::IdComponent flatIndex = flatOffset;
for (vtkm::IdComponent nestIndex = 0; nestIndex < NNest; ++nestIndex)
{
CopyVecFlatToNestedImpl(flatVec, flatIndex, nestedVec[nestIndex]);
flatIndex += subSize;
}
}
template <typename T, vtkm::IdComponent N, typename NestedVecType>
VTKM_EXEC_CONT void CopyVecFlatToNestedImpl(const vtkm::Vec<T, N>& flatVec,
vtkm::IdComponent flatOffset,
NestedVecType& nestedVec)
{
using Traits = vtkm::VecTraits<NestedVecType>;
using ComponentType = typename Traits::ComponentType;
constexpr vtkm::IdComponent subSize = TotalNumComponents<ComponentType>::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 <typename T, vtkm::IdComponent N, typename NestedVecType>
VTKM_EXEC_CONT void CopyVecFlatToNested(const vtkm::Vec<T, N>& flatVec, NestedVecType& nestedVec)
{
detail::CopyVecFlatToNestedImpl(flatVec, 0, nestedVec);
}
} // namespace internal
namespace detail
{
template <typename T>
using VecFlatSuperclass = vtkm::Vec<typename vtkm::VecTraits<T>::BaseComponentType,
vtkm::internal::TotalNumComponents<T>::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<vtkm::Vec<vtkm::Id, 2>, 3>& nestedVec)
/// {
/// auto flatVec = vtkm::make_VecFlat(nestedVec);
/// ```
///
/// `flatVec` is now of type `vtkm::VecFlat<vtkm::Vec<vtkm::Vec<T, 2>, 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 <typename T>
class VecFlat : public detail::VecFlatSuperclass<T>
{
using Superclass = detail::VecFlatSuperclass<T>;
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 <typename T>
VTKM_EXEC_CONT vtkm::VecFlat<T> make_VecFlat(const T& vec)
{
return vtkm::VecFlat<T>(vec);
}
template <typename T>
struct TypeTraits<vtkm::VecFlat<T>> : TypeTraits<detail::VecFlatSuperclass<T>>
{
};
template <typename T>
struct VecTraits<vtkm::VecFlat<T>> : VecTraits<detail::VecFlatSuperclass<T>>
{
};
} // namespace vtkm
#endif //vtk_m_VecFlat_h

@ -42,6 +42,7 @@ set(unit_tests
UnitTestVecFromPortal.cxx
UnitTestVecFromPortalPermute.cxx
UnitTestVectorAnalysis.cxx
UnitTestVecFlat.cxx
UnitTestVecTraits.cxx
UnitTestVecVariable.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 <vtkm/VecFlat.h>
#include <vtkm/VecAxisAlignedPointCoordinates.h>
#include <vtkm/cont/Logging.h>
#include <vtkm/testing/Testing.h>
namespace
{
template <typename T>
void CheckTraits(const T&, vtkm::IdComponent numComponents)
{
VTKM_TEST_ASSERT((std::is_same<typename vtkm::TypeTraits<T>::DimensionalityTag,
vtkm::TypeTraitsVectorTag>::value));
VTKM_TEST_ASSERT(vtkm::VecTraits<T>::NUM_COMPONENTS == numComponents);
}
void TryBasicVec()
{
using NestedVecType = vtkm::Vec<vtkm::Vec<vtkm::Id, 2>, 3>;
std::cout << "Trying " << vtkm::cont::TypeToString<NestedVecType>() << 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<NestedVecType>{ 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<ScalarType>() << 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<vtkm::VecAxisAlignedPointCoordinates<1>, 2>;
std::cout << "Trying " << vtkm::cont::TypeToString<NestedVecType>() << 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);
}