diff --git a/docs/changelog/array-handle-decorator.md b/docs/changelog/array-handle-decorator.md new file mode 100644 index 000000000..99826b58c --- /dev/null +++ b/docs/changelog/array-handle-decorator.md @@ -0,0 +1,244 @@ +# Add `ArrayHandleDecorator`. + +`ArrayHandleDecorator` is given a `DecoratorImpl` class and a list of one or +more source `ArrayHandle`s. There are no restrictions on the size or type of +the source `ArrayHandle`s. + + +The decorator implementation class is described below: + +``` +struct ExampleDecoratorImplementation +{ + + // Takes one portal for each source array handle (only two shown). + // Returns a functor that defines: + // + // 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 + // ArrayHandleDecorator. + // + // Both SomeFunctor::operator() and CreateFunctor must be const. + // + template + 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; + // + // which takes an index and a value, which should be used to modify one + // or more of the source arrays. + // + // CreateInverseFunctor is optional; if not provided, the + // ArrayHandleDecorator will be read-only. In addition, if all of the + // source ArrayHandles are read-only, the inverse functor will not be used + // and the ArrayHandleDecorator will be read only. + // + // Both SomeInverseFunctor::operator() and CreateInverseFunctor must be + // const. + // + template + SomeInverseFunctor CreateInverseFunctor(Portal1Type portal1, + Portal2Type portal2) const; + +}; +``` + +Some example implementation classes are provided below: + +Reverse a ScanExtended: +``` +// Decorator implementation that reverses the ScanExtended operation. +// +// The resulting ArrayHandleDecorator will take an array produced by the +// ScanExtended algorithm and return the original ScanExtended input. +// +// Some interesting things about this: +// - The ArrayHandleDecorator's ValueType will not be the same as the +// ScanPortal's ValueType. The Decorator ValueType is determined by the +// return type of Functor::operator(). +// - The ScanPortal has more values than the ArrayHandleDecorator. The +// number of values the ArrayHandleDecorator should hold is set during +// construction and may differ from the arrays it holds. +template +struct ScanExtendedToNumIndicesDecorImpl +{ + template + struct Functor + { + ScanPortalType ScanPortal; + + VTKM_EXEC_CONT + ValueType operator()(vtkm::Id idx) const + { + return static_cast(this->ScanPortal.Get(idx + 1) - + this->ScanPortal.Get(idx)); + } + }; + + template + Functor CreateFunctor(ScanPortalType portal) const + { + return {portal}; + } +}; + +auto numIndicesOrig = vtkm::cont::make_ArrayHandleCounting(ValueType{0}, + ValueType{1}, + ARRAY_SIZE); +vtkm::cont::ArrayHandle scan; +vtkm::cont::Algorithm::ScanExtended( + vtkm::cont::make_ArrayHandleCast(numIndicesOrig), + scan); +auto numIndicesDecor = vtkm::cont::make_ArrayHandleDecorator( + ARRAY_SIZE, + ScanExtendedToNumIndicesDecorImpl{}, + scan); +``` + +Combine two other `ArrayHandle`s using an arbitrary binary operation: +``` +// Decorator implementation that demonstrates how to create functors that +// hold custom state. Here, the functors have a customizable Operation +// member. +// +// This implementation is used to create a read-only ArrayHandleDecorator +// that combines the values in two other ArrayHandles using an arbitrary +// binary operation (e.g. vtkm::Maximum, vtkm::Add, etc). +template +struct BinaryOperationDecorImpl +{ + OperationType Operation; + + // The functor use to read values. Note that it holds extra state in + // addition to the portals. + template + struct Functor + { + Portal1Type Portal1; + Portal2Type Portal2; + OperationType Operation; + + VTKM_EXEC_CONT + ValueType operator()(vtkm::Id idx) const + { + return this->Operation(static_cast(this->Portal1.Get(idx)), + static_cast(this->Portal2.Get(idx))); + } + }; + + // A non-variadic example of a factory function to produce a functor. This + // is where the extra state is passed into the functor. + template + Functor CreateFunctor(P1T p1, P2T p2) const + { + return {p1, p2, this->Operation}; + } +}; + +BinaryOperationDecorImpl factory{vtkm::Maximum{}}; +auto decorArray = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, + factory, + array1, + array2); +``` + +A factory that does a complex and invertible operation on three portals: + +``` +// Decorator implemenation 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. +// +// Constructs functors that take three portals. +// +// The first portal's values are accessed in reverse order. +// The second portal's values are accessed in normal order. +// The third portal's values are accessed via ((idx + 3) % size). +// +// Functor will return the max of the first two added to the third. +// +// InverseFunctor will update the third portal such that the Functor would +// return the indicated value. +struct InvertibleDecorImpl +{ + + // The functor used for reading data from the three portals. + template + struct Functor + { + using ValueType = typename Portal1Type::ValueType; + + Portal1Type Portal1; + Portal2Type Portal2; + Portal3Type Portal3; + + VTKM_EXEC_CONT ValueType operator()(vtkm::Id idx) const + { + const auto idx1 = this->Portal1.GetNumberOfValues() - idx - 1; + const auto idx2 = idx; + const auto idx3 = (idx + 3) % this->Portal3.GetNumberOfValues(); + + const auto v1 = this->Portal1.Get(idx1); + const auto v2 = this->Portal2.Get(idx2); + const auto v3 = this->Portal3.Get(idx3); + + return vtkm::Max(v1, v2) + v3; + } + }; + + // The functor used for writing. Only Portal3 is written to, the other + // portals may be read-only. + template + struct InverseFunctor + { + using ValueType = typename Portal1Type::ValueType; + + Portal1Type Portal1; + Portal2Type Portal2; + Portal3Type Portal3; + + VTKM_EXEC_CONT void operator()(vtkm::Id idx, const ValueType &vIn) const + { + const auto v1 = this->Portal1.Get(this->Portal1.GetNumberOfValues() - idx - 1); + const auto v2 = this->Portal2.Get(idx); + const auto vNew = static_cast(vIn - vtkm::Max(v1, v2)); + this->Portal3.Set((idx + 3) % this->Portal3.GetNumberOfValues(), vNew); + } + }; + + // Factory function that takes 3 portals as input and creates an instance + // of Functor with them. Variadic template parameters are used here, but are + // not necessary. + template + Functor::type...> + CreateFunctor(PortalTs&&... portals) const + { + VTKM_STATIC_ASSERT(sizeof...(PortalTs) == 3); + return {std::forward(portals)...}; + } + + // Factory function that takes 3 portals as input and creates an instance + // of InverseFunctor with them. Variadic template parameters are used here, + // but are not necessary. + template + InverseFunctor::type...> + CreateInverseFunctor(PortalTs&&... portals) const + { + VTKM_STATIC_ASSERT(sizeof...(PortalTs) == 3); + return {std::forward(portals)...}; + } +}; + +// Note that only ah3 must be writable for ahInv to be writable. ah1 and ah2 +// may be read-only arrays. +auto ahInv = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, + InvertibleDecorImpl{}, + ah1, + ah2, + ah3); +``` diff --git a/vtkm/cont/ArrayHandle.h b/vtkm/cont/ArrayHandle.h index bcefbc880..644d41a5b 100644 --- a/vtkm/cont/ArrayHandle.h +++ b/vtkm/cont/ArrayHandle.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include #include @@ -74,37 +76,15 @@ struct IsInValidArrayHandle { }; -namespace detail -{ - -template -struct IsWritableArrayPortalImpl -{ -private: - template ().Set(0, std::declval()))> - static std::true_type hasSet(int); - - template - static std::false_type hasSet(...); - -public: - using type = decltype(hasSet(0)); -}; - -} // namespace detail - -/// Checks to see if the ArrayHandle or ArrayPortal allows writing, as some -/// ArrayHandles (Implicit) don't support writing. These will be defined as -/// either std::true_type or std::false_type. -/// @{ -template -using IsWritableArrayPortal = typename detail::IsWritableArrayPortalImpl::type; - +/// Checks to see if the ArrayHandle allows writing, as some ArrayHandles +/// (Implicit) don't support writing. These will be defined as either +/// std::true_type or std::false_type. +/// +/// \sa vtkm::internal::PortalSupportsSets +/// template using IsWritableArrayHandle = - IsWritableArrayPortal::type::PortalControl>; + vtkm::internal::PortalSupportsSets::type::PortalControl>; /// @} /// Checks to see if the given object is an array handle. This check is diff --git a/vtkm/cont/ArrayHandleDecorator.h b/vtkm/cont/ArrayHandleDecorator.h new file mode 100644 index 000000000..946b86351 --- /dev/null +++ b/vtkm/cont/ArrayHandleDecorator.h @@ -0,0 +1,828 @@ +//============================================================================ +// 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_ArrayHandleDecorator_h +#define vtk_m_ArrayHandleDecorator_h + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +namespace vtkm +{ +namespace cont +{ +namespace internal +{ + +namespace decor +{ + +// Generic InverseFunctor implementation that does nothing. +struct NoOpInverseFunctor +{ + NoOpInverseFunctor() = default; + template + VTKM_EXEC_CONT NoOpInverseFunctor(Ts...) + { + } + template + VTKM_EXEC_CONT void operator()(vtkm::Id, VT) const + { + } +}; + +} // namespace decor + +// The portal for ArrayHandleDecorator. Get calls FunctorType::operator(), and +// Set calls InverseFunctorType::operator(), but only if the DecoratorImpl +// provides an inverse. +template +class VTKM_ALWAYS_EXPORT ArrayPortalDecorator +{ +public: + using ValueType = ValueType_; + using FunctorType = FunctorType_; + using InverseFunctorType = InverseFunctorType_; + using ReadOnly = std::is_same; + + VTKM_EXEC_CONT + ArrayPortalDecorator() {} + + VTKM_CONT + ArrayPortalDecorator(FunctorType func, InverseFunctorType iFunc, vtkm::Id numValues) + : Functor{ func } + , InverseFunctor{ iFunc } + , NumberOfValues{ numValues } + { + } + + VTKM_EXEC_CONT + vtkm::Id GetNumberOfValues() const { return this->NumberOfValues; } + + VTKM_EXEC_CONT + ValueType Get(vtkm::Id index) const { return this->Functor(index); } + + VTKM_EXEC_CONT + template ::type> + void Set(vtkm::Id index, const ValueType& value) const + { + this->InverseFunctor(index, value); + } + +private: + FunctorType Functor; + InverseFunctorType InverseFunctor; + vtkm::Id NumberOfValues; +}; + +namespace decor +{ + +// Ensures that all types in variadic container ArrayHandleList are subclasses +// of ArrayHandleBase. +template +using AllAreArrayHandles = + brigand::all>; + +namespace detail +{ + +// Tests whether DecoratorImplT has a CreateInverseFunctor(Portals...) method. +template +struct IsFunctorInvertibleImpl; + +template class List, typename... PortalTs> +struct IsFunctorInvertibleImpl> +{ +private: + using PortalList = brigand::list::type...>; + + template < + typename T, + typename U = decltype(std::declval().CreateInverseFunctor(std::declval()...))> + static std::true_type InverseExistsTest(int); + + template + static std::false_type InverseExistsTest(...); + +public: + using type = decltype(InverseExistsTest(0)); +}; + +// Deduces the type returned by DecoratorImplT::CreateFunctor when given +// the specified portals. +template +struct GetFunctorTypeImpl; + +template class List, typename... PortalTs> +struct GetFunctorTypeImpl> +{ + using type = decltype(std::declval().CreateFunctor(std::declval()...)); +}; + +// Deduces the type returned by DecoratorImplT::CreateInverseFunctor when given +// the specified portals. If DecoratorImplT doesn't have a CreateInverseFunctor +// method, a NoOp functor will be used instead. +template +struct GetInverseFunctorTypeImpl; + +template class List, typename... PortalTs> +struct GetInverseFunctorTypeImpl> +{ + using type = + decltype(std::declval().CreateInverseFunctor(std::declval()...)); +}; + +template +struct GetInverseFunctorTypeImpl +{ + using type = NoOpInverseFunctor; +}; + +// Get appropriate portals from a source array. +// See note below about using non-writable portals in invertible functors. +// We need to sub in const portals when writable ones don't exist. +template +typename std::decay::type::PortalControl GetPortalControlImpl(std::true_type, + ArrayT&& array) +{ + return array.GetPortalControl(); +} + +template +typename std::decay::type::PortalConstControl GetPortalControlImpl(std::false_type, + ArrayT&& array) +{ + return array.GetPortalConstControl(); +} + +template +typename std::decay::type::template ExecutionTypes::Portal +GetPortalInPlaceImpl(std::true_type, ArrayT&& array, Device) +{ + return array.PrepareForInPlace(Device{}); +} + +template +typename std::decay::type::template ExecutionTypes::PortalConst +GetPortalInPlaceImpl(std::false_type, ArrayT&& array, Device) +{ + // ArrayT is read-only -- prepare for input instead. + return array.PrepareForInput(Device{}); +} + +template +typename std::decay::type::template ExecutionTypes::Portal +GetPortalOutputImpl(std::true_type, ArrayT&& array, Device) +{ + // Prepare these for inplace usage instead -- we'll likely need to read + // from these in addition to writing. + return array.PrepareForInPlace(Device{}); +} + +template +typename std::decay::type::template ExecutionTypes::PortalConst +GetPortalOutputImpl(std::false_type, ArrayT&& array, Device) +{ + // ArrayT is read-only -- prepare for input instead. + return array.PrepareForInput(Device{}); +} + +} // namespace detail + +// Get portal types: +// We allow writing to an AHDecorator if *any* of the ArrayHandles are writable. +// This means we have to avoid calling PrepareForOutput, etc on non-writable +// array handles, since these may throw. On non-writable handles, use the +// const array handles so we can at least read from them in the inverse +// functors. +template ::type::PortalControl, + typename PortalConst = typename std::decay::type::PortalConstControl> +using GetPortalControlType = + typename brigand::if_, Portal, PortalConst>::type; + +template +using GetPortalConstControlType = typename std::decay::type::PortalConstControl; + +template ::type::template ExecutionTypes::Portal, + typename PortalConst = + typename std::decay::type::template ExecutionTypes::PortalConst> +using GetPortalExecutionType = + typename brigand::if_, Portal, PortalConst>::type; + +template +using GetPortalConstExecutionType = + typename std::decay::type::template ExecutionTypes::PortalConst; + +// Get portal objects: +// See note above -- we swap in const portals sometimes. +template +GetPortalControlType::type> GetPortalControl(ArrayT&& array) +{ + return detail::GetPortalControlImpl(IsWritableArrayHandle{}, std::forward(array)); +} + +template +GetPortalConstControlType::type> GetPortalConstControl( + const ArrayT& array) +{ + return array.GetPortalConstControl(); +} + +template +GetPortalConstExecutionType::type, Device> GetPortalInput( + const ArrayT& array, + Device) +{ + return array.PrepareForInput(Device{}); +} + +template +GetPortalExecutionType::type, Device> GetPortalInPlace(ArrayT&& array, + Device) +{ + return detail::GetPortalInPlaceImpl( + IsWritableArrayHandle{}, std::forward(array), Device{}); +} + +template +GetPortalExecutionType::type, Device> GetPortalOutput(ArrayT&& array, + Device) +{ + return detail::GetPortalOutputImpl( + IsWritableArrayHandle{}, std::forward(array), Device{}); +} + +// Equivalent to std::true_type if *any* portal in PortalList can be written to. +// If all are read-only, std::false_type is used instead. +template +using AnyPortalIsWritable = + typename brigand::any>::type; + +// Set to std::true_type if DecoratorImplT::CreateInverseFunctor can be called +// with the supplied portals, or std::false_type otherwise. +template +using IsFunctorInvertible = + typename detail::IsFunctorInvertibleImpl::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 +using CanWriteToFunctor = typename brigand::and_, + AnyPortalIsWritable>::type; + +// The FunctorType for the provided implementation and portal types. +template +using GetFunctorType = typename detail::GetFunctorTypeImpl::type; + +// The InverseFunctorType for the provided implementation and portal types. +// Will detect when inversion is not possible and return a NoOp functor instead. +template +using GetInverseFunctorType = + typename detail::GetInverseFunctorTypeImpl, + DecoratorImplT, + PortalList>::type; + +// Convert a sequence of array handle types to a list of portals: + +// Some notes on this implementation: +// - MSVC 2015 ICEs when using brigand::transform to convert a brigand::list +// of arrayhandles to portals. So instead we pass the ArrayTs. +// - Just using brigand::list...> fails, as +// apparently that is an improper parameter pack expansion +// - So we jump through some decltype/declval hoops here to get this to work: +template +using GetPortalConstControlList = + brigand::list())))...>; + +template +using GetPortalConstExecutionList = + brigand::list(), Device{})))...>; + +template +using GetPortalControlList = + brigand::list())))...>; + +template +using GetPortalExecutionList = + brigand::list(), Device{})))...>; + +template +struct DecoratorStorageTraits +{ + using ArrayList = brigand::list; + + VTKM_STATIC_ASSERT_MSG(sizeof...(ArrayTs) > 0, + "Must specify at least one source array handle for " + "ArrayHandleDecorator. Consider using " + "ArrayHandleImplicit instead."); + + // Need to check this here, since this traits struct is used in the + // ArrayHandleDecorator superclass definition before any other + // static_asserts could be used. + VTKM_STATIC_ASSERT_MSG(decor::AllAreArrayHandles::value, + "Trailing template parameters for " + "ArrayHandleDecorator must be a list of ArrayHandle " + "types."); + + using ArrayTupleType = vtkmstd::tuple; + + // size_t integral constants that index ArrayTs: + using IndexList = brigand::make_sequence, sizeof...(ArrayTs)>; + + // 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: + using PortalControlList = GetPortalControlList; + using PortalConstControlList = GetPortalConstControlList; + template + using PortalExecutionList = GetPortalExecutionList; + template + using PortalConstExecutionList = GetPortalConstExecutionList; + + // Functors: + using FunctorControlType = GetFunctorType; + using FunctorConstControlType = GetFunctorType; + + template + using FunctorExecutionType = GetFunctorType>; + + template + using FunctorConstExecutionType = + GetFunctorType>; + + // Inverse functors: + using InverseFunctorControlType = GetInverseFunctorType; + + using InverseFunctorConstControlType = NoOpInverseFunctor; + + template + using InverseFunctorExecutionType = + GetInverseFunctorType>; + + template + using InverseFunctorConstExecutionType = NoOpInverseFunctor; + + // Misc: + // ValueType is derived from DecoratorImplT::CreateFunctor(...)'s operator(). + using ValueType = decltype(std::declval()(0)); + + // Decorator portals: + using PortalControlType = + ArrayPortalDecorator; + + using PortalConstControlType = + ArrayPortalDecorator; + + template + using PortalExecutionType = ArrayPortalDecorator, + InverseFunctorExecutionType>; + + template + using PortalConstExecutionType = ArrayPortalDecorator, + InverseFunctorConstExecutionType>; + + // helper for constructing portals with the appropriate functors. This is + // where we decide whether or not to call `CreateInverseFunctor` on the + // implementation class. + // Do not use these directly, they are helpers for the MakePortal[...] + // methods below. + template + VTKM_CONT static + typename std::enable_if::type + CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs&&... portals) + { // Portal is read only: + return { impl.CreateFunctor(std::forward(portals)...), + typename DecoratorPortalType::InverseFunctorType{}, + numVals }; + } + + template + VTKM_CONT static + typename std::enable_if::type + CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs... portals) + { // Portal is read/write: + return { impl.CreateFunctor(portals...), impl.CreateInverseFunctor(portals...), numVals }; + } + + // Portal construction methods. These actually create portals. + template