//============================================================================ // 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 #include #include #include #include #include namespace { struct DecoratorTests { static constexpr vtkm::Id ARRAY_SIZE = 10; // 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. // // 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)... }; } }; // Same as above, but cannot be inverted. The resulting ArrayHandleDecorator // will be read-only. struct NonInvertibleDecorImpl { 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 v1 = this->Portal1.Get(this->Portal1.GetNumberOfValues() - idx - 1); const auto v2 = this->Portal2.Get(idx); const auto v3 = this->Portal3.Get((idx + 3) % this->Portal3.GetNumberOfValues()); return vtkm::Max(v1, v2) + v3; } }; template Functor::type...> CreateFunctor(PortalTs&&... portals) const { VTKM_STATIC_ASSERT(sizeof...(PortalTs) == 3); return { std::forward(portals)... }; } }; // 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 }; } }; // 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 }; } }; // Decorator implementation that combines two source arrays using the formula // `[source1] * 10 + [source2]` and supports resizing. template struct DecompositionDecorImpl { template struct Functor { Portal1T Portal1; Portal2T Portal2; VTKM_EXEC_CONT ValueType operator()(vtkm::Id idx) const { return static_cast(this->Portal1.Get(idx) * 10 + this->Portal2.Get(idx)); } }; template struct InverseFunctor { Portal1T Portal1; Portal2T Portal2; VTKM_EXEC_CONT void operator()(vtkm::Id idx, const ValueType& val) const { this->Portal1.Set(idx, static_cast(std::floor(val / 10))); this->Portal2.Set(idx, static_cast(std::fmod(val, 10))); } }; template VTKM_CONT Functor::type, typename std::decay::type> CreateFunctor(Portal1T&& p1, Portal2T&& p2) const { return { std::forward(p1), std::forward(p2) }; } template VTKM_CONT InverseFunctor::type, typename std::decay::type> CreateInverseFunctor(Portal1T&& p1, Portal2T&& p2) const { return { std::forward(p1), std::forward(p2) }; } // Resize methods: template VTKM_CONT void AllocateSourceArrays(vtkm::Id numVals, vtkm::CopyFlag preserve, vtkm::cont::Token& token, Array1T&& array1, Array2T&& array2) const { array1.Allocate(numVals, preserve, token); array2.Allocate(numVals, preserve, token); } }; template void InversionTest() const { auto ah1 = vtkm::cont::make_ArrayHandleCounting(ValueType{ 0 }, ValueType{ 2 }, ARRAY_SIZE); auto ah2 = vtkm::cont::make_ArrayHandleConstant(ValueType{ ARRAY_SIZE }, ARRAY_SIZE); vtkm::cont::ArrayHandle ah3; ah3.AllocateAndFill(ARRAY_SIZE, ValueType{ ARRAY_SIZE / 2 }); auto ah3Const = vtkm::cont::make_ArrayHandleConstant(ValueType{ ARRAY_SIZE / 2 }, ARRAY_SIZE); { // Has a writable handle and an invertible functor: auto ahInv = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, InvertibleDecorImpl{}, ah1, ah2, ah3); VTKM_TEST_ASSERT(vtkm::cont::internal::IsWritableArrayHandle::value); } { // Has no writable handles and an invertible functor: auto ahNInv = vtkm::cont::make_ArrayHandleDecorator( ARRAY_SIZE, InvertibleDecorImpl{}, ah1, ah2, ah3Const); VTKM_TEST_ASSERT(!vtkm::cont::internal::IsWritableArrayHandle::value); } { // Has writable handles, but the functor cannot be inverted: auto ahNInv = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, NonInvertibleDecorImpl{}, ah1, ah2, ah3); VTKM_TEST_ASSERT(!vtkm::cont::internal::IsWritableArrayHandle::value); } { // Has no writable handles and the functor cannot be inverted: auto ahNInv = vtkm::cont::make_ArrayHandleDecorator( ARRAY_SIZE, NonInvertibleDecorImpl{}, ah1, ah2, ah3Const); VTKM_TEST_ASSERT(!vtkm::cont::internal::IsWritableArrayHandle::value); } { // Test reading/writing to an invertible handle: // Copy ah3 since we'll be modifying it: vtkm::cont::ArrayHandle ah3Copy; vtkm::cont::ArrayCopy(ah3, ah3Copy); auto ahDecor = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, InvertibleDecorImpl{}, ah1, ah2, ah3Copy); { auto portalDecor = ahDecor.ReadPortal(); VTKM_TEST_ASSERT(ahDecor.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalDecor.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalDecor.Get(0) == ValueType{ 23 }); VTKM_TEST_ASSERT(portalDecor.Get(1) == ValueType{ 21 }); VTKM_TEST_ASSERT(portalDecor.Get(2) == ValueType{ 19 }); VTKM_TEST_ASSERT(portalDecor.Get(3) == ValueType{ 17 }); VTKM_TEST_ASSERT(portalDecor.Get(4) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalDecor.Get(5) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalDecor.Get(6) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalDecor.Get(7) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalDecor.Get(8) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalDecor.Get(9) == ValueType{ 15 }); } // Copy a constant array into the decorator. This should modify ah3Copy. vtkm::cont::ArrayCopyDevice(vtkm::cont::make_ArrayHandleConstant(ValueType{ 25 }, ARRAY_SIZE), ahDecor); { // Accessing portal should give all 25s: auto portalDecor = ahDecor.ReadPortal(); VTKM_TEST_ASSERT(ahDecor.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalDecor.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalDecor.Get(0) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(1) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(2) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(3) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(4) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(5) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(6) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(7) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(8) == ValueType{ 25 }); VTKM_TEST_ASSERT(portalDecor.Get(9) == ValueType{ 25 }); } { // ah3Copy should have updated values: auto portalAH3Copy = ah3Copy.ReadPortal(); VTKM_TEST_ASSERT(ahDecor.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalAH3Copy.GetNumberOfValues() == ARRAY_SIZE); VTKM_TEST_ASSERT(portalAH3Copy.Get(0) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(1) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(2) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(3) == ValueType{ 7 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(4) == ValueType{ 9 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(5) == ValueType{ 11 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(6) == ValueType{ 13 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(7) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(8) == ValueType{ 15 }); VTKM_TEST_ASSERT(portalAH3Copy.Get(9) == ValueType{ 15 }); } } } template void BinaryOperatorTest() const { auto ahCount = vtkm::cont::make_ArrayHandleCounting(ValueType{ 0 }, ValueType{ 1 }, ARRAY_SIZE); auto ahConst = vtkm::cont::make_ArrayHandleConstant(ValueType{ ARRAY_SIZE / 2 }, ARRAY_SIZE); const OperationType op; BinaryOperationDecorImpl impl{ op }; auto decorArray = vtkm::cont::make_ArrayHandleDecorator(ARRAY_SIZE, impl, ahCount, ahConst); { auto decorPortal = decorArray.ReadPortal(); auto countPortal = ahCount.ReadPortal(); auto constPortal = ahConst.ReadPortal(); for (vtkm::Id i = 0; i < ARRAY_SIZE; ++i) { VTKM_TEST_ASSERT(decorPortal.Get(i) == op(countPortal.Get(i), constPortal.Get(i))); } } vtkm::cont::ArrayHandle copiedInExec; vtkm::cont::ArrayCopyDevice(decorArray, copiedInExec); { auto copiedPortal = copiedInExec.ReadPortal(); auto countPortal = ahCount.ReadPortal(); auto constPortal = ahConst.ReadPortal(); for (vtkm::Id i = 0; i < ARRAY_SIZE; ++i) { VTKM_TEST_ASSERT(copiedPortal.Get(i) == op(countPortal.Get(i), constPortal.Get(i))); } } } template void ScanExtendedToNumIndicesTest() const { 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); // Some interesting things to notice: // - `numIndicesDecor` will have `ARRAY_SIZE` entries, while `scan` has // `ARRAY_SIZE + 1`. // - `numIndicesDecor` uses the current function scope `ValueType`, since // that is what the functor from the implementation class returns. `scan` // uses `vtkm::Id`. auto numIndicesDecor = vtkm::cont::make_ArrayHandleDecorator( ARRAY_SIZE, ScanExtendedToNumIndicesDecorImpl{}, scan); { auto origPortal = numIndicesOrig.ReadPortal(); auto decorPortal = numIndicesDecor.ReadPortal(); VTKM_STATIC_ASSERT(VTKM_PASS_COMMAS( std::is_same::value)); VTKM_TEST_ASSERT(origPortal.GetNumberOfValues() == decorPortal.GetNumberOfValues()); for (vtkm::Id i = 0; i < origPortal.GetNumberOfValues(); ++i) { VTKM_TEST_ASSERT(origPortal.Get(i) == decorPortal.Get(i)); } } } template void DecompositionTest() const { vtkm::cont::ArrayHandle a1; vtkm::cont::ArrayHandle a2; auto decor = vtkm::cont::make_ArrayHandleDecorator(0, DecompositionDecorImpl{}, a1, a2); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 0); decor.Allocate(5); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5); { auto decorPortal = decor.WritePortal(); 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.ReadPortal(); 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.ReadPortal(); 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.Allocate(3, vtkm::CopyFlag::On); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 3); { auto decorPortal = decor.ReadPortal(); 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.ReadPortal(); 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.ReadPortal(); 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 void operator()(const ValueType) const { this->InversionTest(); this->BinaryOperatorTest(); this->BinaryOperatorTest(); this->BinaryOperatorTest(); this->BinaryOperatorTest(); this->BinaryOperatorTest(); this->ScanExtendedToNumIndicesTest(); this->DecompositionTest(); } }; // ArrayHandleDecorator that implements AllocateSourceArrays, thus allowing // it to be resized. struct ResizableDecorImpl { // We don't actually read/write from this, so use a dummy functor: struct Functor { VTKM_EXEC_CONT vtkm::Id operator()(vtkm::Id) const { return 0; } }; template VTKM_CONT Functor CreateFunctor(PortalTs...) const { return Functor{}; } template void AllocateSourceArrays(vtkm::Id newSize, vtkm::CopyFlag preserve, vtkm::cont::Token& token, Array1T& a1, Array2T& a2) const { VTKM_IS_ARRAY_HANDLE(Array1T); VTKM_IS_ARRAY_HANDLE(Array2T); // Resize differently based on preserve to verify the flag is correct. if (preserve == vtkm::CopyFlag::Off) { // Resize each to 3*newSize: a1.Allocate(3 * newSize, preserve, token); a2.Allocate(3 * newSize, preserve, token); } else { // Resize each to 2*newSize: a1.Allocate(2 * newSize, vtkm::CopyFlag::On); a2.Allocate(2 * newSize, vtkm::CopyFlag::On); } } }; // ArrayHandleDecorator that does not implement AllocateSourceArrays, thus not allowing // it to be resized. struct NonResizableDecorImpl { // We don't actually read/write from this, so use a dummy functor: struct Functor { VTKM_EXEC_CONT vtkm::Id operator()(vtkm::Id) const { return 0; } }; template VTKM_CONT Functor CreateFunctor(PortalTs...) const { return Functor{}; } }; void ResizeTest() { { vtkm::cont::ArrayHandle a1; vtkm::cont::ArrayHandle a2; ResizableDecorImpl impl; auto decor = vtkm::cont::make_ArrayHandleDecorator(5, impl, a1, a2); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5); decor.Allocate(10); // Should allocate a1&a2 to have 30 values: VTKM_TEST_ASSERT(a1.GetNumberOfValues() == 30); VTKM_TEST_ASSERT(a2.GetNumberOfValues() == 30); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 10); decor.Allocate(3, vtkm::CopyFlag::On); // Should resize a1&a2 to have 6 values: VTKM_TEST_ASSERT(a1.GetNumberOfValues() == 6); VTKM_TEST_ASSERT(a2.GetNumberOfValues() == 6); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 3); } { vtkm::cont::ArrayHandle a1; a1.Allocate(20); vtkm::cont::ArrayHandle a2; a2.Allocate(20); NonResizableDecorImpl impl; auto decor = vtkm::cont::make_ArrayHandleDecorator(5, impl, a1, a2); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5); // Allocate and Shrink should throw an ErrorBadType: bool threw = false; try { decor.Allocate(10); } catch (vtkm::cont::ErrorBadType& e) { std::cerr << "Caught expected exception: " << e.what() << "\n"; threw = true; } VTKM_TEST_ASSERT(threw, "Allocate did not throw as expected."); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5); threw = false; try { decor.Allocate(3, vtkm::CopyFlag::On); } catch (vtkm::cont::ErrorBadType& e) { std::cerr << "Caught expected exception: " << e.what() << "\n"; threw = true; } VTKM_TEST_ASSERT(threw, "Allocate did not throw as expected."); VTKM_TEST_ASSERT(decor.GetNumberOfValues() == 5); } } void TestArrayHandleDecorator() { vtkm::testing::Testing::TryTypes(DecoratorTests{}, vtkm::TypeListScalarAll{}); ResizeTest(); } } // anonymous namespace int UnitTestArrayHandleDecorator(int argc, char* argv[]) { return vtkm::cont::testing::Testing::Run(TestArrayHandleDecorator, argc, argv); }