Add vtkm/Algorithms.h header with device-friendly binary search algorithms.

This commit is contained in:
Allison Vacanti 2019-12-05 14:45:58 -05:00
parent 6d4e37e95c
commit 44c4f0838f
13 changed files with 575 additions and 4 deletions

@ -0,0 +1,72 @@
# Algorithms for Control and Execution Environments
The `<vtkm/Algorithms.h>` header has been added to provide common STL-style
generic algorithms that are suitable for use in both the control and execution
environments. This is necessary as the STL algorithms in the `<algorithm>`
header are not marked up for use in execution environments such as CUDA.
In addition to the markup, these algorithms have convenience overloads to
support ArrayPortals directly, simplifying their usage with VTK-m data
structures.
Currently, three related algorithms are provided: `LowerBounds`, `UpperBounds`,
and `BinarySearch`. `BinarySearch` differs from the STL `std::binary_search`
algorithm in that it returns an iterator (or index) to a matching element,
rather than just a boolean indicating whether a or not key is present.
The new algorithm signatures are:
```c++
namespace vtkm
{
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT
IterT BinarySearch(IterT first, IterT last, const T& val, Comp comp);
template <typename IterT, typename T>
VTKM_EXEC_CONT
IterT BinarySearch(IterT first, IterT last, const T& val);
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT
vtkm::Id BinarySearch(const PortalT& portal, const T& val, Comp comp);
template <typename PortalT, typename T>
VTKM_EXEC_CONT
vtkm::Id BinarySearch(const PortalT& portal, const T& val);
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT
IterT LowerBound(IterT first, IterT last, const T& val, Comp comp);
template <typename IterT, typename T>
VTKM_EXEC_CONT
IterT LowerBound(IterT first, IterT last, const T& val);
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT
vtkm::Id LowerBound(const PortalT& portal, const T& val, Comp comp);
template <typename PortalT, typename T>
VTKM_EXEC_CONT
vtkm::Id LowerBound(const PortalT& portal, const T& val);
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT
IterT UpperBound(IterT first, IterT last, const T& val, Comp comp);
template <typename IterT, typename T>
VTKM_EXEC_CONT
IterT UpperBound(IterT first, IterT last, const T& val);
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT
vtkm::Id UpperBound(const PortalT& portal, const T& val, Comp comp);
template <typename PortalT, typename T>
VTKM_EXEC_CONT
vtkm::Id UpperBound(const PortalT& portal, const T& val);
}
```

191
vtkm/Algorithms.h Normal file

@ -0,0 +1,191 @@
//============================================================================
// 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_Algorithms_h
#define vtk_m_Algorithms_h
#include <vtkm/cont/ArrayPortalToIterators.h>
#include <vtkm/BinaryPredicates.h>
#include <vtkm/internal/Configure.h>
#include <algorithm>
#include <iterator>
namespace vtkm
{
/// Similar to std::lower_bound and std::upper_bound, but returns an iterator
/// to any matching item (rather than a specific one). Returns @a last when
/// @a val is not found.
/// @{
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT IterT BinarySearch(IterT first, IterT last, const T& val, Comp comp)
{
auto len = last - first;
while (len != 0)
{
const auto halfLen = len / 2;
IterT mid = first + halfLen;
if (comp(*mid, val))
{
first = mid + 1;
len -= halfLen + 1;
}
else if (comp(val, *mid))
{
len = halfLen;
}
else
{
return mid; // found element
}
}
return last; // did not find element
}
template <typename IterT, typename T>
VTKM_EXEC_CONT IterT BinarySearch(IterT first, IterT last, const T& val)
{
return vtkm::BinarySearch(first, last, val, vtkm::SortLess{});
}
/// @}
/// Similar to std::lower_bound and std::upper_bound, but returns the index of
/// any matching item (rather than a specific one). Returns -1 when @a val is not
/// found.
/// @{
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT vtkm::Id BinarySearch(const PortalT& portal, const T& val, Comp comp)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::BinarySearch(first, last, val, comp);
return result == last ? static_cast<vtkm::Id>(-1) : static_cast<vtkm::Id>(result - first);
}
// Return -1 if not found
template <typename PortalT, typename T>
VTKM_EXEC_CONT vtkm::Id BinarySearch(const PortalT& portal, const T& val)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::BinarySearch(first, last, val, vtkm::SortLess{});
return result == last ? static_cast<vtkm::Id>(-1) : static_cast<vtkm::Id>(result - first);
}
/// @}
/// Implementation of std::lower_bound or std::upper_bound that is appropriate
/// for both control and execution environments.
/// The overloads that take portals return indices instead of iterators.
/// @{
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT IterT LowerBound(IterT first, IterT last, const T& val, Comp comp)
{
#ifdef VTKM_CUDA
auto len = last - first;
while (len != 0)
{
const auto halfLen = len / 2;
IterT mid = first + halfLen;
if (comp(*mid, val))
{
first = mid + 1;
len -= halfLen + 1;
}
else
{
len = halfLen;
}
}
return first;
#else // VTKM_CUDA
return std::lower_bound(first, last, val, std::move(comp));
#endif // VTKM_CUDA
}
template <typename IterT, typename T>
VTKM_EXEC_CONT IterT LowerBound(IterT first, IterT last, const T& val)
{
return vtkm::LowerBound(first, last, val, vtkm::SortLess{});
}
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT vtkm::Id LowerBound(const PortalT& portal, const T& val, Comp comp)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::LowerBound(first, last, val, comp);
return static_cast<vtkm::Id>(result - first);
}
template <typename PortalT, typename T>
VTKM_EXEC_CONT vtkm::Id LowerBound(const PortalT& portal, const T& val)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::LowerBound(first, last, val, vtkm::SortLess{});
return static_cast<vtkm::Id>(result - first);
}
template <typename IterT, typename T, typename Comp>
VTKM_EXEC_CONT IterT UpperBound(IterT first, IterT last, const T& val, Comp comp)
{
#ifdef VTKM_CUDA
auto len = last - first;
while (len != 0)
{
const auto halfLen = len / 2;
IterT mid = first + halfLen;
if (!comp(val, *mid))
{
first = mid + 1;
len -= halfLen + 1;
}
else
{
len = halfLen;
}
}
return first;
#else // VTKM_CUDA
return std::upper_bound(first, last, val, std::move(comp));
#endif // VTKM_CUDA
}
template <typename IterT, typename T>
VTKM_EXEC_CONT IterT UpperBound(IterT first, IterT last, const T& val)
{
return vtkm::UpperBound(first, last, val, vtkm::SortLess{});
}
template <typename PortalT, typename T, typename Comp>
VTKM_EXEC_CONT vtkm::Id UpperBound(const PortalT& portal, const T& val, Comp comp)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::UpperBound(first, last, val, comp);
return static_cast<vtkm::Id>(result - first);
}
template <typename PortalT, typename T>
VTKM_EXEC_CONT vtkm::Id UpperBound(const PortalT& portal, const T& val)
{
auto first = vtkm::cont::ArrayPortalToIteratorBegin(portal);
auto last = vtkm::cont::ArrayPortalToIteratorEnd(portal);
auto result = vtkm::UpperBound(first, last, val, vtkm::SortLess{});
return static_cast<vtkm::Id>(result - first);
}
/// @}
} // end namespace vtkm
#endif // vtk_m_Algorithms_h

@ -17,6 +17,7 @@ vtkm_install_headers(
vtkm ${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/Version.h)
set(headers
Algorithms.h
Assert.h
BinaryPredicates.h
BinaryOperators.h

@ -9,6 +9,7 @@
##============================================================================
set(unit_tests
UnitTestCudaAlgorithms.cu
UnitTestCudaArrayHandle.cu
UnitTestCudaArrayHandleFancy.cu
UnitTestCudaArrayHandleMultiplexer.cu

@ -0,0 +1,20 @@
//============================================================================
// 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/testing/Testing.h>
#include <vtkm/testing/TestingAlgorithms.h>
#include <vtkm/cont/cuda/DeviceAdapterCuda.h>
int UnitTestCudaAlgorithms(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(
RunAlgorithmsTests<vtkm::cont::DeviceAdapterTagCuda>, argc, argv);
}

@ -10,6 +10,7 @@
#ifndef vtk_m_cont_internal_FunctorsGeneral_h
#define vtk_m_cont_internal_FunctorsGeneral_h
#include <vtkm/Algorithms.h>
#include <vtkm/BinaryOperators.h>
#include <vtkm/TypeTraits.h>
#include <vtkm/UnaryPredicates.h>
@ -667,7 +668,7 @@ struct LowerBoundsKernel
using InputIteratorsType = vtkm::cont::ArrayPortalToIterators<InputPortalType>;
InputIteratorsType inputIterators(this->InputPortal);
auto resultPos = std::lower_bound(
auto resultPos = vtkm::LowerBound(
inputIterators.GetBegin(), inputIterators.GetEnd(), this->ValuesPortal.Get(index));
vtkm::Id resultIndex =
@ -715,7 +716,7 @@ struct LowerBoundsComparisonKernel
using InputIteratorsType = vtkm::cont::ArrayPortalToIterators<InputPortalType>;
InputIteratorsType inputIterators(this->InputPortal);
auto resultPos = std::lower_bound(inputIterators.GetBegin(),
auto resultPos = vtkm::LowerBound(inputIterators.GetBegin(),
inputIterators.GetEnd(),
this->ValuesPortal.Get(index),
this->CompareFunctor);
@ -1018,7 +1019,7 @@ struct UpperBoundsKernel
using InputIteratorsType = vtkm::cont::ArrayPortalToIterators<InputPortalType>;
InputIteratorsType inputIterators(this->InputPortal);
auto resultPos = std::upper_bound(
auto resultPos = vtkm::UpperBound(
inputIterators.GetBegin(), inputIterators.GetEnd(), this->ValuesPortal.Get(index));
vtkm::Id resultIndex =
@ -1066,7 +1067,7 @@ struct UpperBoundsKernelComparisonKernel
using InputIteratorsType = vtkm::cont::ArrayPortalToIterators<InputPortalType>;
InputIteratorsType inputIterators(this->InputPortal);
auto resultPos = std::upper_bound(inputIterators.GetBegin(),
auto resultPos = vtkm::UpperBound(inputIterators.GetBegin(),
inputIterators.GetEnd(),
this->ValuesPortal.Get(index),
this->CompareFunctor);

@ -9,6 +9,7 @@
##============================================================================
set(unit_tests
UnitTestOpenMPAlgorithms.cxx
UnitTestOpenMPArrayHandle.cxx
UnitTestOpenMPArrayHandleFancy.cxx
UnitTestOpenMPArrayHandleMultiplexer.cxx

@ -0,0 +1,20 @@
//============================================================================
// 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/testing/Testing.h>
#include <vtkm/testing/TestingAlgorithms.h>
#include <vtkm/cont/openmp/DeviceAdapterOpenMP.h>
int UnitTestOpenMPAlgorithms(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(
RunAlgorithmsTests<vtkm::cont::DeviceAdapterTagOpenMP>, argc, argv);
}

@ -9,6 +9,7 @@
##============================================================================
set(unit_tests
UnitTestTBBAlgorithms.cxx
UnitTestTBBArrayHandle.cxx
UnitTestTBBArrayHandleFancy.cxx
UnitTestTBBArrayHandleMultiplexer.cxx

@ -0,0 +1,20 @@
//============================================================================
// 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/testing/Testing.h>
#include <vtkm/testing/TestingAlgorithms.h>
#include <vtkm/cont/tbb/DeviceAdapterTBB.h>
int UnitTestTBBAlgorithms(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(
RunAlgorithmsTests<vtkm::cont::DeviceAdapterTagTBB>, argc, argv);
}

@ -10,6 +10,7 @@
set(headers
Testing.h
TestingAlgorithms.h
TestingMath.h
TestingGeometry.h
VecTraitsTests.h
@ -18,6 +19,7 @@ set(headers
VTKM_declare_headers(${headers})
set(unit_tests
UnitTestAlgorithms.cxx
UnitTestBinaryPredicates.cxx
UnitTestBinaryOperators.cxx
UnitTestBounds.cxx

@ -0,0 +1,221 @@
//============================================================================
// 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_testing_TestingAlgorithms_h
#define vtk_m_testing_TestingAlgorithms_h
#include <vtkm/Algorithms.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/DeviceAdapterAlgorithm.h>
#include <vtkm/exec/FunctorBase.h>
#include <vtkm/testing/Testing.h>
#include <vector>
namespace
{
using IdArray = vtkm::cont::ArrayHandle<vtkm::Id>;
struct TestBinarySearch
{
template <typename NeedlesT, typename HayStackT, typename ResultsT>
struct Impl : public vtkm::exec::FunctorBase
{
NeedlesT Needles;
HayStackT HayStack;
ResultsT Results;
VTKM_CONT
Impl(const NeedlesT& needles, const HayStackT& hayStack, const ResultsT& results)
: Needles(needles)
, HayStack(hayStack)
, Results(results)
{
}
VTKM_EXEC
void operator()(vtkm::Id index) const
{
this->Results.Set(index, vtkm::BinarySearch(this->HayStack, this->Needles.Get(index)));
}
};
template <typename Device>
static void Run()
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<Device>;
std::vector<vtkm::Id> needlesData{ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 };
std::vector<vtkm::Id> hayStackData{ -3, -2, -2, -2, 0, 0, 1, 1, 1, 4, 4 };
std::vector<bool> expectedFound{
false, true, true, false, true, true, false, false, true, false
};
IdArray needles = vtkm::cont::make_ArrayHandle(needlesData);
IdArray hayStack = vtkm::cont::make_ArrayHandle(hayStackData);
IdArray results;
using Functor = Impl<typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::Portal>;
Functor functor{ needles.PrepareForInput(Device{}),
hayStack.PrepareForInput(Device{}),
results.PrepareForOutput(needles.GetNumberOfValues(), Device{}) };
Algo::Schedule(functor, needles.GetNumberOfValues());
// Verify:
auto needlesPortal = needles.GetPortalConstControl();
auto hayStackPortal = hayStack.GetPortalConstControl();
auto resultsPortal = results.GetPortalConstControl();
for (vtkm::Id i = 0; i < needles.GetNumberOfValues(); ++i)
{
if (expectedFound[static_cast<size_t>(i)])
{
const auto resIdx = resultsPortal.Get(i);
const auto expVal = needlesPortal.Get(i);
VTKM_TEST_ASSERT(resIdx >= 0);
VTKM_TEST_ASSERT(hayStackPortal.Get(resIdx) == expVal);
}
else
{
VTKM_TEST_ASSERT(resultsPortal.Get(i) == -1);
}
}
}
};
struct TestLowerBound
{
template <typename NeedlesT, typename HayStackT, typename ResultsT>
struct Impl : public vtkm::exec::FunctorBase
{
NeedlesT Needles;
HayStackT HayStack;
ResultsT Results;
VTKM_CONT
Impl(const NeedlesT& needles, const HayStackT& hayStack, const ResultsT& results)
: Needles(needles)
, HayStack(hayStack)
, Results(results)
{
}
VTKM_EXEC
void operator()(vtkm::Id index) const
{
this->Results.Set(index, vtkm::LowerBound(this->HayStack, this->Needles.Get(index)));
}
};
template <typename Device>
static void Run()
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<Device>;
std::vector<vtkm::Id> needlesData{ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 };
std::vector<vtkm::Id> hayStackData{ -3, -2, -2, -2, 0, 0, 1, 1, 1, 4, 4 };
std::vector<vtkm::Id> expected{ 0, 0, 1, 4, 4, 6, 9, 9, 9, 11 };
IdArray needles = vtkm::cont::make_ArrayHandle(needlesData);
IdArray hayStack = vtkm::cont::make_ArrayHandle(hayStackData);
IdArray results;
using Functor = Impl<typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::Portal>;
Functor functor{ needles.PrepareForInput(Device{}),
hayStack.PrepareForInput(Device{}),
results.PrepareForOutput(needles.GetNumberOfValues(), Device{}) };
Algo::Schedule(functor, needles.GetNumberOfValues());
// Verify:
auto resultsPortal = results.GetPortalConstControl();
for (vtkm::Id i = 0; i < needles.GetNumberOfValues(); ++i)
{
VTKM_TEST_ASSERT(resultsPortal.Get(i) == expected[static_cast<size_t>(i)]);
}
}
};
struct TestUpperBound
{
template <typename NeedlesT, typename HayStackT, typename ResultsT>
struct Impl : public vtkm::exec::FunctorBase
{
NeedlesT Needles;
HayStackT HayStack;
ResultsT Results;
VTKM_CONT
Impl(const NeedlesT& needles, const HayStackT& hayStack, const ResultsT& results)
: Needles(needles)
, HayStack(hayStack)
, Results(results)
{
}
VTKM_EXEC
void operator()(vtkm::Id index) const
{
this->Results.Set(index, vtkm::UpperBound(this->HayStack, this->Needles.Get(index)));
}
};
template <typename Device>
static void Run()
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<Device>;
std::vector<vtkm::Id> needlesData{ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 };
std::vector<vtkm::Id> hayStackData{ -3, -2, -2, -2, 0, 0, 1, 1, 1, 4, 4 };
std::vector<vtkm::Id> expected{ 0, 1, 4, 4, 6, 9, 9, 9, 11, 11 };
IdArray needles = vtkm::cont::make_ArrayHandle(needlesData);
IdArray hayStack = vtkm::cont::make_ArrayHandle(hayStackData);
IdArray results;
using Functor = Impl<typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::PortalConst,
typename IdArray::ExecutionTypes<Device>::Portal>;
Functor functor{ needles.PrepareForInput(Device{}),
hayStack.PrepareForInput(Device{}),
results.PrepareForOutput(needles.GetNumberOfValues(), Device{}) };
Algo::Schedule(functor, needles.GetNumberOfValues());
// Verify:
auto resultsPortal = results.GetPortalConstControl();
for (vtkm::Id i = 0; i < needles.GetNumberOfValues(); ++i)
{
VTKM_TEST_ASSERT(resultsPortal.Get(i) == expected[static_cast<size_t>(i)]);
}
}
};
} // anon namespace
template <typename Device>
void RunAlgorithmsTests()
{
std::cout << "Testing binary search." << std::endl;
TestBinarySearch::Run<Device>();
std::cout << "Testing lower bound." << std::endl;
TestLowerBound::Run<Device>();
std::cout << "Testing upper bound." << std::endl;
TestUpperBound::Run<Device>();
}
#endif //vtk_m_testing_TestingAlgorithms_h

@ -0,0 +1,20 @@
//============================================================================
// 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/testing/Testing.h>
#include <vtkm/testing/TestingAlgorithms.h>
#include <vtkm/cont/serial/DeviceAdapterSerial.h>
int UnitTestAlgorithms(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(
RunAlgorithmsTests<vtkm::cont::DeviceAdapterTagSerial>, argc, argv);
}