Add StableSortIndices worklet.

This worklet produces an index array that permutes a key array into a
stable sorted order.
This commit is contained in:
Allison Vacanti 2017-09-22 11:54:02 -04:00
parent 8e94e2ae52
commit d6b2896ad9
3 changed files with 295 additions and 2 deletions

@ -33,6 +33,8 @@
#include <vtkm/cont/internal/DeviceAdapterError.h>
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/worklet/StableSortIndices.h>
#include <algorithm>
#include <cmath>
#include <random>
@ -60,11 +62,15 @@ enum BenchmarkName
SCAN_EXCLUSIVE = 1 << 6,
SORT = 1 << 7,
SORT_BY_KEY = 1 << 8,
UNIQUE = 1 << 9,
UPPER_BOUNDS = 1 << 10,
STABLE_SORT_INDICES = 1 << 9,
STABLE_SORT_INDICES_UNIQUE = 1 << 10,
UNIQUE = 1 << 11,
UPPER_BOUNDS = 1 << 12,
ALL = COPY | COPY_IF | LOWER_BOUNDS | REDUCE | REDUCE_BY_KEY | SCAN_INCLUSIVE | SCAN_EXCLUSIVE |
SORT |
SORT_BY_KEY |
STABLE_SORT_INDICES |
STABLE_SORT_INDICES_UNIQUE |
UNIQUE |
UPPER_BOUNDS
};
@ -380,6 +386,7 @@ private:
VTKM_MAKE_BENCHMARK(ReduceByKey20, BenchReduceByKey, 20);
VTKM_MAKE_BENCHMARK(ReduceByKey25, BenchReduceByKey, 25);
VTKM_MAKE_BENCHMARK(ReduceByKey30, BenchReduceByKey, 30);
VTKM_MAKE_BENCHMARK(ReduceByKey100, BenchReduceByKey, 100);
template <typename Value>
struct BenchScanInclusive
@ -526,6 +533,94 @@ private:
VTKM_MAKE_BENCHMARK(SortByKey20, BenchSortByKey, 20);
VTKM_MAKE_BENCHMARK(SortByKey25, BenchSortByKey, 25);
VTKM_MAKE_BENCHMARK(SortByKey30, BenchSortByKey, 30);
VTKM_MAKE_BENCHMARK(SortByKey100, BenchSortByKey, 100);
template <typename Value>
struct BenchStableSortIndices
{
using SSI = vtkm::worklet::StableSortIndices<DeviceAdapterTag>;
using ValueArrayHandle = vtkm::cont::ArrayHandle<Value, StorageTag>;
ValueArrayHandle ValueHandle;
std::mt19937 Rng;
VTKM_CONT
BenchStableSortIndices()
{
this->ValueHandle.Allocate(ARRAY_SIZE);
auto portal = this->ValueHandle.GetPortalControl();
for (vtkm::Id i = 0; i < portal.GetNumberOfValues(); ++i)
{
portal.Set(vtkm::Id(i), TestValue(vtkm::Id(Rng()), Value()));
}
}
VTKM_CONT
vtkm::Float64 operator()()
{
vtkm::cont::ArrayHandle<vtkm::Id> indices;
Algorithm::Copy(vtkm::cont::ArrayHandleIndex(ARRAY_SIZE), indices);
Timer timer;
SSI::Sort(ValueHandle, indices);
return timer.GetElapsedTime();
}
VTKM_CONT
std::string Description() const
{
std::stringstream description;
description << "StableSortIndices::Sort on " << ARRAY_SIZE << " random values";
return description.str();
}
};
VTKM_MAKE_BENCHMARK(StableSortIndices, BenchStableSortIndices);
template <typename Value>
struct BenchStableSortIndicesUnique
{
using SSI = vtkm::worklet::StableSortIndices<DeviceAdapterTag>;
using IndexArrayHandle = typename SSI::IndexArrayType;
using ValueArrayHandle = vtkm::cont::ArrayHandle<Value, StorageTag>;
const vtkm::Id N_VALID;
ValueArrayHandle ValueHandle;
VTKM_CONT
BenchStableSortIndicesUnique(vtkm::Id percent_valid)
: N_VALID((ARRAY_SIZE * percent_valid) / 100)
{
Algorithm::Schedule(
FillModuloTestValueKernel<Value>(
N_VALID, this->ValueHandle.PrepareForOutput(ARRAY_SIZE, DeviceAdapterTag())),
ARRAY_SIZE);
}
VTKM_CONT
vtkm::Float64 operator()()
{
IndexArrayHandle indices = SSI::Sort(this->ValueHandle);
Timer timer;
SSI::Unique(this->ValueHandle, indices);
return timer.GetElapsedTime();
}
VTKM_CONT
std::string Description() const
{
std::stringstream description;
description << "StableSortIndices::Unique on " << ARRAY_SIZE << " values with "
<< this->N_VALID << " valid values";
return description.str();
}
};
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique5, BenchStableSortIndicesUnique, 5);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique10, BenchStableSortIndicesUnique, 10);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique15, BenchStableSortIndicesUnique, 15);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique20, BenchStableSortIndicesUnique, 20);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique25, BenchStableSortIndicesUnique, 25);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique30, BenchStableSortIndicesUnique, 30);
VTKM_MAKE_BENCHMARK(StableSortIndicesUnique100, BenchStableSortIndicesUnique, 100);
template <typename Value>
struct BenchUnique
@ -674,6 +769,7 @@ public:
VTKM_RUN_BENCHMARK(ReduceByKey20, ValueTypes());
VTKM_RUN_BENCHMARK(ReduceByKey25, ValueTypes());
VTKM_RUN_BENCHMARK(ReduceByKey30, ValueTypes());
VTKM_RUN_BENCHMARK(ReduceByKey100, ValueTypes());
}
if (benchmarks & SCAN_INCLUSIVE)
@ -703,6 +799,25 @@ public:
VTKM_RUN_BENCHMARK(SortByKey20, ValueTypes());
VTKM_RUN_BENCHMARK(SortByKey25, ValueTypes());
VTKM_RUN_BENCHMARK(SortByKey30, ValueTypes());
VTKM_RUN_BENCHMARK(SortByKey100, ValueTypes());
}
if (benchmarks & STABLE_SORT_INDICES)
{
std::cout << "\n" << DIVIDER << "\nBenchmarking StableSortIndices::Sort\n";
VTKM_RUN_BENCHMARK(StableSortIndices, ValueTypes());
}
if (benchmarks & STABLE_SORT_INDICES_UNIQUE)
{
std::cout << "\n" << DIVIDER << "\nBenchmarking StableSortIndices::Unique\n";
VTKM_RUN_BENCHMARK(StableSortIndicesUnique5, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique10, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique15, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique20, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique25, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique30, ValueTypes());
VTKM_RUN_BENCHMARK(StableSortIndicesUnique100, ValueTypes());
}
if (benchmarks & UNIQUE)
@ -783,6 +898,14 @@ int main(int argc, char* argv[])
{
benchmarks |= vtkm::benchmarking::SORT_BY_KEY;
}
else if (arg == "stablesortindices")
{
benchmarks |= vtkm::benchmarking::STABLE_SORT_INDICES;
}
else if (arg == "stablesortindicesunique")
{
benchmarks |= vtkm::benchmarking::STABLE_SORT_INDICES_UNIQUE;
}
else if (arg == "unique")
{
benchmarks |= vtkm::benchmarking::UNIQUE;

@ -57,6 +57,7 @@ set(headers
ScatterIdentity.h
ScatterPermutation.h
ScatterUniform.h
StableSortIndices.h
StreamLineUniformGrid.h
SurfaceNormals.h
Tetrahedralize.h

@ -0,0 +1,169 @@
//============================================================================
// 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.
//
// Copyright 2017 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2017 UT-Battelle, LLC.
// Copyright 2017 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#ifndef vtk_m_worklet_SortAndUniqueIndices_h
#define vtk_m_worklet_SortAndUniqueIndices_h
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/DeviceAdapterAlgorithm.h>
namespace vtkm
{
namespace worklet
{
/// Produces an ArrayHandle<vtkm::Id> index array that stable-sorts and
/// optionally uniquifies an input array.
template <typename DeviceAdapter>
struct StableSortIndices
{
using IndexArrayType = vtkm::cont::ArrayHandle<vtkm::Id>;
// Allows Sort to be called on an array that indexes into KeyPortal.
// If the values compare equal, the indices are compared to stabilize the
// result.
template <typename KeyPortalType>
struct IndirectSortPredicate
{
using KeyType = typename KeyPortalType::ValueType;
const KeyPortalType KeyPortal;
VTKM_CONT
IndirectSortPredicate(const KeyPortalType& keyPortal)
: KeyPortal(keyPortal)
{
}
template <typename IndexType>
VTKM_EXEC bool operator()(const IndexType& a, const IndexType& b) const
{
// If the values compare equal, compare the indices as well so we get
// consistent outputs.
const KeyType valueA = this->KeyPortal.Get(a);
const KeyType valueB = this->KeyPortal.Get(b);
if (valueA < valueB)
{
return true;
}
else if (valueB < valueA)
{
return false;
}
else
{
return a < b;
}
}
};
// Allows Unique to be called on an array that indexes into KeyPortal.
template <typename KeyPortalType>
struct IndirectUniquePredicate
{
const KeyPortalType KeyPortal;
VTKM_CONT
IndirectUniquePredicate(const KeyPortalType& keyPortal)
: KeyPortal(keyPortal)
{
}
template <typename IndexType>
VTKM_EXEC bool operator()(const IndexType& a, const IndexType& b) const
{
return this->KeyPortal.Get(a) == this->KeyPortal.Get(b);
}
};
/// Permutes the @a indices array so that it will map @a keys into a stable
/// sorted order. The @a keys array is not modified.
///
/// @param keys The ArrayHandle containing data to be sorted.
/// @param indices The ArrayHandle<vtkm::Id> containing the permutation indices.
///
/// @note @a indices is expected to contain the values (0, numKeys] in
/// increasing order. If the values in @a indices are not sequential, the sort
/// will succeed and be consistently reproducible, but the result is not
/// guaranteed to be stable with respect to the original ordering of @a keys.
template <typename KeyType, typename Storage>
VTKM_CONT static void Sort(const vtkm::cont::ArrayHandle<KeyType, Storage>& keys,
IndexArrayType& indices)
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>;
using KeyArrayType = vtkm::cont::ArrayHandle<KeyType, Storage>;
using KeyPortalType =
typename KeyArrayType::template ExecutionTypes<DeviceAdapter>::PortalConst;
using SortPredicate = IndirectSortPredicate<KeyPortalType>;
VTKM_ASSERT(keys.GetNumberOfValues() == indices.GetNumberOfValues());
KeyPortalType keyPortal = keys.PrepareForInput(DeviceAdapter());
Algo::Sort(indices, SortPredicate(keyPortal));
}
/// Returns an index array that maps the @a keys array into a stable sorted
/// ordering. The @a keys array is not modified.
///
/// This is a convenience overload that generates the index array.
template <typename KeyType, typename Storage>
VTKM_CONT static IndexArrayType Sort(const vtkm::cont::ArrayHandle<KeyType, Storage>& keys)
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>;
using KeyArrayType = vtkm::cont::ArrayHandle<KeyType, Storage>;
using KeyPortalType =
typename KeyArrayType::template ExecutionTypes<DeviceAdapter>::PortalConst;
using SortPredicate = IndirectSortPredicate<KeyPortalType>;
// Generate the initial index array
IndexArrayType indices;
{
vtkm::cont::ArrayHandleIndex indicesSrc(keys.GetNumberOfValues());
Algo::Copy(indicesSrc, indices);
}
KeyPortalType keyPortal = keys.PrepareForInput(DeviceAdapter());
Algo::Sort(indices, SortPredicate(keyPortal));
return indices;
}
/// Reduces the array returned by @a Sort so that the mapped @a keys are
/// unique. The @a indices array will be modified in-place and the @a keys
/// array is not modified.
///
template <typename KeyType, typename Storage>
VTKM_CONT static void Unique(const vtkm::cont::ArrayHandle<KeyType, Storage>& keys,
IndexArrayType& indices)
{
using Algo = vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>;
using KeyArrayType = vtkm::cont::ArrayHandle<KeyType, Storage>;
using KeyPortalType =
typename KeyArrayType::template ExecutionTypes<DeviceAdapter>::PortalConst;
using UniquePredicate = IndirectUniquePredicate<KeyPortalType>;
KeyPortalType keyPortal = keys.PrepareForInput(DeviceAdapter());
Algo::Unique(indices, UniquePredicate(keyPortal));
}
};
}
} // end namespace vtkm::worklet
#endif // vtk_m_worklet_SortAndUniqueIndices_h