Commit 90afd1ef authored by Allison Vacanti's avatar Allison Vacanti Committed by Kitware Robot
Browse files

Merge topic '428_decorator_improvements'

88bf38af Add support for ArrayHandleDecorator resizing.
5834c285 Add cont/exec markup to ArrayHandleDecorator docs.
5b128060

 Cleanup reference types when using declval.
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: Kenneth Moreland's avatarKenneth Moreland <kmorel@sandia.gov>
Merge-request: !1933
parents c9dae5a5 88bf38af
Pipeline #155512 canceled with stage
in 0 seconds
# ArrayHandleDecorator Allocate and Shrink Support
`ArrayHandleDecorator` can now be resized when given an appropriate
decorator implementation.
Since the mapping between the size of an `ArrayHandleDecorator` and its source
`ArrayHandle`s is not well defined, resize operations (such as `Shrink` and
`Allocate`) are not defined by default, and will throw an exception if called.
However, by implementing the methods `AllocateSourceArrays` and/or
`ShrinkSourceArrays` on the implementation class, resizing the decorator is
allowed. These methods are passed in a new size along with each of the
`ArrayHandleDecorator`'s source arrays, allowing developers to control how
the resize operation should affect the source arrays.
For example, the following decorator implementation can be used to create a
resizable `ArrayHandleDecorator` that is implemented using two arrays, which
are combined to produce values via the expression:
```
[decorator value i] = [source1 value i] * 10 + [source2 value i]
```
Implementation:
```c++
template <typename ValueType>
struct DecompositionDecorImpl
{
template <typename Portal1T, typename Portal2T>
struct Functor
{
Portal1T Portal1;
Portal2T Portal2;
VTKM_EXEC_CONT
ValueType operator()(vtkm::Id idx) const
{
return static_cast<ValueType>(this->Portal1.Get(idx) * 10 + this->Portal2.Get(idx));
}
};
template <typename Portal1T, typename Portal2T>
struct InverseFunctor
{
Portal1T Portal1;
Portal2T Portal2;
VTKM_EXEC_CONT
void operator()(vtkm::Id idx, const ValueType& val) const
{
this->Portal1.Set(idx, static_cast<ValueType>(std::floor(val / 10)));
this->Portal2.Set(idx, static_cast<ValueType>(std::fmod(val, 10)));
}
};
template <typename Portal1T, typename Portal2T>
VTKM_CONT Functor<typename std::decay<Portal1T>::type, typename std::decay<Portal2T>::type>
CreateFunctor(Portal1T&& p1, Portal2T&& p2) const
{
return { std::forward<Portal1T>(p1), std::forward<Portal2T>(p2) };
}
template <typename Portal1T, typename Portal2T>
VTKM_CONT InverseFunctor<typename std::decay<Portal1T>::type, typename std::decay<Portal2T>::type>
CreateInverseFunctor(Portal1T&& p1, Portal2T&& p2) const
{
return { std::forward<Portal1T>(p1), std::forward<Portal2T>(p2) };
}
// Resize methods:
template <typename Array1T, typename Array2T>
VTKM_CONT
void AllocateSourceArrays(vtkm::Id numVals, Array1T&& array1, Array2T&& array2) const
{
array1.Allocate(numVals);
array2.Allocate(numVals);
}
template <typename Array1T, typename Array2T>
VTKM_CONT
void ShrinkSourceArrays(vtkm::Id numVals, Array1T&& array1, Array2T&& array2) const
{
array1.Shrink(numVals);
array2.Shrink(numVals);
}
};
// Usage:
vtkm::cont::ArrayHandle<ValueType> a1;
vtkm::cont::ArrayHandle<ValueType> a2;
auto decor = vtkm::cont::make_ArrayHandleDecorator(0, DecompositionDecorImpl<ValueType>{}, a1, a2);
decor.Allocate(5);
{
auto decorPortal = decor.GetPortalControl();
decorPortal.Set(0, 13);
decorPortal.Set(1, 8);
decorPortal.Set(2, 43);
decorPortal.Set(3, 92);
decorPortal.Set(4, 117);
}
// a1: { 1, 0, 4, 9, 11 }
// a2: { 3, 8, 3, 2, 7 }
// decor: { 13, 8, 43, 92, 117 }
decor.Shrink(3);
// a1: { 1, 0, 4 }
// a2: { 3, 8, 3 }
// decor: { 13, 8, 43 }
```
......@@ -11,6 +11,7 @@
#define vtk_m_ArrayHandleDecorator_h
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ErrorBadType.h>
#include <vtkm/cont/Storage.h>
#include <vtkm/StaticAssert.h>
......@@ -125,11 +126,9 @@ template <typename DecoratorImplT, template <typename...> class List, typename..
struct IsFunctorInvertibleImpl<DecoratorImplT, List<PortalTs...>>
{
private:
using PortalList = brigand::list<typename std::decay<PortalTs>::type...>;
template <
typename T,
typename U = decltype(std::declval<T>().CreateInverseFunctor(std::declval<PortalTs>()...))>
typename U = decltype(std::declval<T>().CreateInverseFunctor(std::declval<PortalTs&&>()...))>
static std::true_type InverseExistsTest(int);
template <typename T>
......@@ -139,6 +138,44 @@ public:
using type = decltype(InverseExistsTest<DecoratorImplT>(0));
};
// Tests whether DecoratorImplT has an AllocateSourceArrays(size, Arrays...) method.
template <typename DecoratorImplT, typename ArrayList>
struct IsDecoratorAllocatableImpl;
template <typename DecoratorImplT, template <typename...> class List, typename... ArrayTs>
struct IsDecoratorAllocatableImpl<DecoratorImplT, List<ArrayTs...>>
{
private:
template <
typename T,
typename U = decltype(std::declval<T>().AllocateSourceArrays(0, std::declval<ArrayTs&>()...))>
static std::true_type Exists(int);
template <typename T>
static std::false_type Exists(...);
public:
using type = decltype(Exists<DecoratorImplT>(0));
};
// Tests whether DecoratorImplT has a ShrinkSourceArrays(size, Arrays...) method.
template <typename DecoratorImplT, typename ArrayList>
struct IsDecoratorShrinkableImpl;
template <typename DecoratorImplT, template <typename...> class List, typename... ArrayTs>
struct IsDecoratorShrinkableImpl<DecoratorImplT, List<ArrayTs...>>
{
private:
template <
typename T,
typename U = decltype(std::declval<T>().ShrinkSourceArrays(0, std::declval<ArrayTs&>()...))>
static std::true_type Exists(int);
template <typename T>
static std::false_type Exists(...);
public:
using type = decltype(Exists<DecoratorImplT>(0));
};
// Deduces the type returned by DecoratorImplT::CreateFunctor when given
// the specified portals.
template <typename DecoratorImplT, typename PortalList>
......@@ -147,7 +184,8 @@ struct GetFunctorTypeImpl;
template <typename DecoratorImplT, template <typename...> class List, typename... PortalTs>
struct GetFunctorTypeImpl<DecoratorImplT, List<PortalTs...>>
{
using type = decltype(std::declval<DecoratorImplT>().CreateFunctor(std::declval<PortalTs>()...));
using type =
decltype(std::declval<DecoratorImplT>().CreateFunctor(std::declval<PortalTs&&>()...));
};
// Deduces the type returned by DecoratorImplT::CreateInverseFunctor when given
......@@ -160,7 +198,7 @@ template <typename DecoratorImplT, template <typename...> class List, typename..
struct GetInverseFunctorTypeImpl<std::true_type, DecoratorImplT, List<PortalTs...>>
{
using type =
decltype(std::declval<DecoratorImplT>().CreateInverseFunctor(std::declval<PortalTs>()...));
decltype(std::declval<DecoratorImplT>().CreateInverseFunctor(std::declval<PortalTs&&>()...));
};
template <typename DecoratorImplT, typename PortalList>
......@@ -300,6 +338,18 @@ template <typename DecoratorImplT, typename PortalList>
using IsFunctorInvertible =
typename detail::IsFunctorInvertibleImpl<DecoratorImplT, PortalList>::type;
// Set to std::true_type if DecoratorImplT::AllocateSourceArrays can be called
// with the supplied arrays, or std::false_type otherwise.
template <typename DecoratorImplT, typename ArrayList>
using IsDecoratorAllocatable =
typename detail::IsDecoratorAllocatableImpl<DecoratorImplT, ArrayList>::type;
// Set to std::true_type if DecoratorImplT::ShrinkSourceArrays can be called
// with the supplied arrays, or std::false_type otherwise.
template <typename DecoratorImplT, typename ArrayList>
using IsDecoratorShrinkable =
typename detail::IsDecoratorShrinkableImpl<DecoratorImplT, ArrayList>::type;
// std::true_type/std::false_type depending on whether the decorator impl has a
// CreateInversePortal method AND any of the arrays are writable.
template <typename DecoratorImplT, typename PortalList>
......@@ -328,19 +378,19 @@ using GetInverseFunctorType =
// - So we jump through some decltype/declval hoops here to get this to work:
template <typename... ArrayTs>
using GetPortalConstControlList =
brigand::list<decltype((GetPortalConstControl(std::declval<ArrayTs>())))...>;
brigand::list<decltype((GetPortalConstControl(std::declval<ArrayTs&>())))...>;
template <typename Device, typename... ArrayTs>
using GetPortalConstExecutionList =
brigand::list<decltype((GetPortalInput(std::declval<ArrayTs>(), Device{})))...>;
brigand::list<decltype((GetPortalInput(std::declval<ArrayTs&>(), Device{})))...>;
template <typename... ArrayTs>
using GetPortalControlList =
brigand::list<decltype((GetPortalControl(std::declval<ArrayTs>())))...>;
brigand::list<decltype((GetPortalControl(std::declval<ArrayTs&>())))...>;
template <typename Device, typename... ArrayTs>
using GetPortalExecutionList =
brigand::list<decltype((GetPortalInPlace(std::declval<ArrayTs>(), Device{})))...>;
brigand::list<decltype((GetPortalInPlace(std::declval<ArrayTs&>(), Device{})))...>;
template <typename DecoratorImplT, typename... ArrayTs>
struct DecoratorStorageTraits
......@@ -369,6 +419,10 @@ struct DecoratorStorageTraits
using IndexList = tao::seq::make_index_sequence<sizeof...(ArrayTs)>;
#endif // VTKM_USE_TAO_SEQ
// true_type/false_type depending on whether the decorator supports Allocate/Shrink:
using IsAllocatable = IsDecoratorAllocatable<DecoratorImplT, ArrayList>;
using IsShrinkable = IsDecoratorShrinkable<DecoratorImplT, ArrayList>;
// Portal lists:
// NOTE we have to pass the parameter pack here instead of using ArrayList
// with brigand::transform, since that's causing MSVC 2015 to ice:
......@@ -446,6 +500,41 @@ struct DecoratorStorageTraits
return { impl.CreateFunctor(portals...), impl.CreateInverseFunctor(portals...), numVals };
}
// Static dispatch for calling AllocateSourceArrays on supported implementations:
VTKM_CONT[[noreturn]] static void CallAllocate(std::false_type,
const DecoratorImplT&,
vtkm::Id,
ArrayTs&...)
{
throw vtkm::cont::ErrorBadType("Allocate not supported by this ArrayHandleDecorator.");
}
VTKM_CONT static void CallAllocate(std::true_type,
const DecoratorImplT& impl,
vtkm::Id newSize,
ArrayTs&... arrays)
{
impl.AllocateSourceArrays(newSize, arrays...);
}
// Static dispatch for calling ShrinkSourceArrays on supported implementations.
VTKM_CONT[[noreturn]] static void CallShrink(std::false_type,
const DecoratorImplT&,
vtkm::Id,
ArrayTs&...)
{
throw vtkm::cont::ErrorBadType("Shrink not supported by this ArrayHandleDecorator.");
}
VTKM_CONT static void CallShrink(std::true_type,
const DecoratorImplT& impl,
vtkm::Id newSize,
ArrayTs&... arrays)
{
impl.ShrinkSourceArrays(newSize, arrays...);
}
#ifndef VTKM_USE_TAO_SEQ
// Portal construction methods. These actually create portals.
template <template <typename...> class List, typename... Indices>
......@@ -527,7 +616,26 @@ struct DecoratorStorageTraits
GetPortalOutput(vtkmstd::get<Indices{}.value>(arrays), dev)...);
}
#else // VTKM_USE_TAO_SEQ
template <template <typename...> class List, typename... Indices>
VTKM_CONT static void AllocateSourceArrays(const DecoratorImplT& impl,
ArrayTupleType& arrays,
vtkm::Id numValues,
List<Indices...>)
{
CallAllocate(IsAllocatable{}, impl, numValues, vtkmstd::get<Indices{}.value>(arrays)...);
}
template <template <typename...> class List, typename... Indices>
VTKM_CONT static void ShrinkSourceArrays(const DecoratorImplT& impl,
ArrayTupleType& arrays,
vtkm::Id numValues,
List<Indices...>)
{
CallShrink(IsShrinkable{}, impl, numValues, vtkmstd::get<Indices{}.value>(arrays)...);
}
#else // VTKM_USE_TAO_SEQ
// Portal construction methods. These actually create portals.
template <template <typename, std::size_t...> class List, std::size_t... Indices>
VTKM_CONT static PortalControlType MakePortalControl(const DecoratorImplT& impl,
......@@ -581,6 +689,25 @@ struct DecoratorStorageTraits
return CreatePortalDecorator<PortalExecutionType<Device>>(
numValues, impl, GetPortalOutput(vtkmstd::get<Indices>(arrays), dev)...);
}
template <template <typename, std::size_t...> class List, std::size_t... Indices>
VTKM_CONT static void AllocateSourceArrays(const DecoratorImplT& impl,
ArrayTupleType& arrays,
vtkm::Id numValues,
List<std::size_t, Indices...>)
{
CallAllocate(IsAllocatable{}, impl, numValues, vtkmstd::get<Indices>(arrays)...);
}
template <template <typename, std::size_t...> class List, std::size_t... Indices>
VTKM_CONT static void ShrinkSourceArrays(const DecoratorImplT& impl,
ArrayTupleType& arrays,
vtkm::Id numValues,
List<std::size_t, Indices...>)
{
CallShrink(IsShrinkable{}, impl, numValues, vtkmstd::get<Indices>(arrays)...);
}
#endif // VTKM_USE_TAO_SEQ
};
......@@ -643,18 +770,21 @@ public:
}
VTKM_CONT
void Allocate(vtkm::Id)
void Allocate(vtkm::Id numValues)
{
VTKM_ASSERT(this->Valid);
// No-op. I suppose eventually we could pass numValues down to the
// implementation class and let it do something intelligent.
Traits::AllocateSourceArrays(this->Implementation, this->ArrayTuple, numValues, IndexList{});
// If the above call doesn't throw, update our state.
this->NumberOfValues = numValues;
}
VTKM_CONT
void Shrink(vtkm::Id)
void Shrink(vtkm::Id numValues)
{
VTKM_ASSERT(this->Valid);
// No-op. Again, could eventually be passed down to the implementation.
Traits::ShrinkSourceArrays(this->Implementation, this->ArrayTuple, numValues, IndexList{});
// If the above call doesn't throw, update our state.
this->NumberOfValues = numValues;
}
VTKM_CONT
......@@ -779,10 +909,7 @@ public:
}
VTKM_CONT
void Shrink(vtkm::Id)
{
// no-op
}
void Shrink(vtkm::Id numValues) { this->Storage->Shrink(numValues); }
VTKM_CONT
......@@ -813,7 +940,7 @@ private:
/// // Takes one portal for each source array handle (only two shown).
/// // Returns a functor that defines:
/// //
/// // ValueType operator()(vtkm::Id id) const;
/// // VTKM_EXEC_CONT ValueType operator()(vtkm::Id id) const;
/// //
/// // which takes an index and returns a value which should be produced by
/// // the source arrays somehow. This ValueType will be the ValueType of the
......@@ -822,12 +949,13 @@ private:
/// // Both SomeFunctor::operator() and CreateFunctor must be const.
/// //
/// template <typename Portal1Type, typename Portal2Type>
/// VTKM_CONT
/// SomeFunctor CreateFunctor(Portal1Type portal1, Portal2Type portal2) const;
///
/// // Takes one portal for each source array handle (only two shown).
/// // Returns a functor that defines:
/// //
/// // void operator()(vtkm::Id id, ValueType val) const;
/// // VTKM_EXEC_CONT void operator()(vtkm::Id id, ValueType val) const;
/// //
/// // which takes an index and a value, which should be used to modify one
/// // or more of the source arrays.
......@@ -841,9 +969,35 @@ private:
/// // const.
/// //
/// template <typename Portal1Type, typename Portal2Type>
/// VTKM_CONT
/// SomeInverseFunctor CreateInverseFunctor(Portal1Type portal1,
/// Portal2Type portal2) const;
///
/// // Given a set of ArrayHandles and a size, implement what should happen
/// // to the source ArrayHandles when Allocate() is called on the decorator
/// // handle.
/// //
/// // AllocateSourceArrays is optional; if not provided, the
/// // ArrayHandleDecorator will throw if its Allocate method is called. If
/// // an implementation is present and doesn't throw, the
/// // ArrayHandleDecorator's internal state is updated to show `size` as the
/// // number of values.
/// template <typename Array1Type, typename Array2Type>
/// VTKM_CONT
/// void AllocateSourceArrays(vtkm::Id size, Array1Type array1, Array2Type array2) const;
///
/// // Given a set of ArrayHandles and a size, implement what should happen to
/// // the source ArrayHandles when Shrink() is called on the decorator handle.
/// //
/// // ShrinkSourceArrays is optional; if not provided, the
/// // ArrayHandleDecorator will throw if its Shrink method is called. If
/// // an implementation is present and doesn't throw, the
/// // ArrayHandleDecorator's internal state is updated to show `size` as the
/// // number of values.
/// template <typename Array1Type, typename Array2Type>
/// VTKM_CONT
/// void ShrinkSourceArrays(vtkm::Id size, Array1Type array1, Array2Type array2) const;
///
/// };
/// ```
///
......
......@@ -27,7 +27,7 @@ struct DecoratorTests
{
static constexpr vtkm::Id ARRAY_SIZE = 10;
// Decorator implemenation that demonstrates how to write invertible functors
// Decorator implementation that demonstrates how to write invertible functors
// that combine three array handles with complex access logic. The resulting
// ArrayHandleDecorator can be both read from and written to.
//
......@@ -212,6 +212,69 @@ struct DecoratorTests
}
};
// Decorator implementation that combines two source arrays using the formula
// `[source1] * 10 + [source2]` and supports resizing.
template <typename ValueType>
struct DecompositionDecorImpl
{
template <typename Portal1T, typename Portal2T>
struct Functor
{
Portal1T Portal1;
Portal2T Portal2;
VTKM_EXEC_CONT
ValueType operator()(vtkm::Id idx) const
{
return static_cast<ValueType>(this->Portal1.Get(idx) * 10 + this->Portal2.Get(idx));
}
};
template <typename Portal1T, typename Portal2T>
struct InverseFunctor
{
Portal1T Portal1;
Portal2T Portal2;
VTKM_EXEC_CONT
void operator()(vtkm::Id idx, const ValueType& val) const
{
this->Portal1.Set(idx, static_cast<ValueType>(std::floor(val / 10)));
this->Portal2.Set(idx, static_cast<ValueType>(std::fmod(val, 10)));
}
};
template <typename Portal1T, typename Portal2T>
VTKM_CONT Functor<typename std::decay<Portal1T>::type, typename std::decay<Portal2T>::type>
CreateFunctor(Portal1T&& p1, Portal2T&& p2) const
{
return { std::forward<Portal1T>(p1), std::forward<Portal2T>(p2) };
}
template <typename Portal1T, typename Portal2T>
VTKM_CONT
InverseFunctor<typename std::decay<Portal1T>::type, typename std::decay<Portal2T>::type>
CreateInverseFunctor(Portal1T&& p1, Portal2T&& p2) const
{
return { std::forward<Portal1T>(p1), std::forward<Portal2T>(p2) };
}
// Resize methods:
template <typename Array1T, typename Array2T>
VTKM_CONT void AllocateSourceArrays(vtkm::Id numVals, Array1T&& array1, Array2T&& array2) const
{
array1.Allocate(numVals);
array2.Allocate(numVals);
}
template <typename Array1T, typename Array2T>
VTKM_CONT void ShrinkSourceArrays(vtkm::Id numVals, Array1T&& array1, Array2T&& array2) const
{
array1.Shrink(numVals);
array2.Shrink(numVals);
}
};
template <typename ValueType>
void InversionTest() const
{
......@@ -374,24 +437,210 @@ struct DecoratorTests
}
}
template <typename ValueType>
void DecompositionTest() const
{
vtkm::cont::ArrayHandle<ValueType> a1;
vtkm::cont::ArrayHandle<ValueType> a2;
auto decor =
vtkm::cont::make_ArrayHandleDecorator(0, DecompositionDecorImpl<ValueType>{}, a1, a2);
VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 0);
decor.Allocate(5);
VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5);
{
auto decorPortal = decor.GetPortalControl();
decorPortal.Set(0, 13);
decorPortal.Set(1, 8);
decorPortal.Set(2, 43);
decorPortal.Set(3, 92);
decorPortal.Set(4, 117);
}
VTKM_TEST_ASSERT(a1.GetNumberOfValues() == 5);
{
auto a1Portal = a1.GetPortalConstControl();
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(0), 1));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(1), 0));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(2), 4));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(3), 9));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(4), 11));
}
VTKM_TEST_ASSERT(a2.GetNumberOfValues() == 5);
{
auto a2Portal = a2.GetPortalConstControl();
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(0), 3));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(1), 8));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(2), 3));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(3), 2));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(4), 7));
}
decor.Shrink(3);
VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 3);
{
auto decorPortal = decor.GetPortalConstControl();
VTKM_TEST_ASSERT(test_equal(decorPortal.Get(0), 13));
VTKM_TEST_ASSERT(test_equal(decorPortal.Get(1), 8));
VTKM_TEST_ASSERT(test_equal(decorPortal.Get(2), 43));
}
VTKM_TEST_ASSERT(a1.GetNumberOfValues() == 3);
{
auto a1Portal = a1.GetPortalConstControl();
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(0), 1));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(1), 0));
VTKM_TEST_ASSERT(test_equal(a1Portal.Get(2), 4));
}
VTKM_TEST_ASSERT(a2.GetNumberOfValues() == 3);
{
auto a2Portal = a2.GetPortalConstControl();
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(0), 3));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(1), 8));
VTKM_TEST_ASSERT(test_equal(a2Portal.Get(2), 3));
}
}
template <typename ValueType>
void operator()(const ValueType) const
{
InversionTest<ValueType>();
this->InversionTest<ValueType>();
this->BinaryOperatorTest<ValueType, vtkm::Maximum>();
this->BinaryOperatorTest<ValueType, vtkm::Minimum>();
this->BinaryOperatorTest<ValueType, vtkm::Add>();
this->BinaryOperatorTest<ValueType, vtkm::Subtract>();