From 3b57dc06de0d3d1e910c436b7499222f09ff3822 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Mon, 3 Feb 2025 14:25:36 -0500 Subject: [PATCH 1/2] Add ArraySetValues --- vtkm/cont/ArraySetValues.cxx | 76 ++++++ vtkm/cont/ArraySetValues.h | 261 +++++++++++++++++++ vtkm/cont/CMakeLists.txt | 2 + vtkm/cont/testing/CMakeLists.txt | 1 + vtkm/cont/testing/UnitTestArraySetValues.cxx | 185 +++++++++++++ 5 files changed, 525 insertions(+) create mode 100644 vtkm/cont/ArraySetValues.cxx create mode 100644 vtkm/cont/ArraySetValues.h create mode 100644 vtkm/cont/testing/UnitTestArraySetValues.cxx diff --git a/vtkm/cont/ArraySetValues.cxx b/vtkm/cont/ArraySetValues.cxx new file mode 100644 index 0000000000..54c8ebd321 --- /dev/null +++ b/vtkm/cont/ArraySetValues.cxx @@ -0,0 +1,76 @@ +//============================================================================ +// 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 + +void vtkm::cont::internal::ArraySetValuesImpl(const vtkm::cont::UnknownArrayHandle& ids, + const vtkm::cont::UnknownArrayHandle& values, + const vtkm::cont::UnknownArrayHandle& data, + std::false_type) +{ + auto idArray = ids.ExtractComponent(0, vtkm::CopyFlag::On); + VTKM_ASSERT(ids.GetNumberOfValues() == values.GetNumberOfValues()); + + bool copied = false; + vtkm::ListForEach( + [&](auto base) { + using T = decltype(base); + if (!copied && data.IsBaseComponentType()) + { + vtkm::IdComponent numComponents = data.GetNumberOfComponentsFlat(); + VTKM_ASSERT(values.GetNumberOfComponentsFlat() == numComponents); + + for (vtkm::IdComponent componentIdx = 0; componentIdx < numComponents; ++componentIdx) + { + auto valuesArray = values.ExtractComponent(componentIdx, vtkm::CopyFlag::On); + auto dataArray = data.ExtractComponent(componentIdx, vtkm::CopyFlag::Off); + auto permutedArray = vtkm::cont::make_ArrayHandlePermutation(idArray, dataArray); + + bool copiedComponent = false; + copiedComponent = vtkm::cont::TryExecute([&](auto device) { + if (dataArray.IsOnDevice(device)) + { + vtkm::cont::DeviceAdapterAlgorithm::Copy(valuesArray, + permutedArray); + return true; + } + return false; + }); + + if (!copiedComponent) + { // Fallback to control-side copy + const vtkm::Id numVals = ids.GetNumberOfValues(); + auto inPortal = valuesArray.ReadPortal(); + auto outPortal = permutedArray.WritePortal(); + for (vtkm::Id i = 0; i < numVals; ++i) + { + outPortal.Set(i, inPortal.Get(i)); + } + } + } + + copied = true; + } + }, + vtkm::TypeListBaseC{}); + + if (!copied) + { + throw vtkm::cont::ErrorBadType("Unable to set values in array of type " + + data.GetArrayTypeName()); + } +} diff --git a/vtkm/cont/ArraySetValues.h b/vtkm/cont/ArraySetValues.h new file mode 100644 index 0000000000..fa7f882444 --- /dev/null +++ b/vtkm/cont/ArraySetValues.h @@ -0,0 +1,261 @@ +//============================================================================ +// 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_cont_ArraySetValues_h +#define vtk_m_cont_ArraySetValues_h + +#include + +#include +#include + +#include +#include + +namespace vtkm +{ +namespace cont +{ + +namespace internal +{ + +VTKM_CONT_EXPORT void ArraySetValuesImpl(const vtkm::cont::UnknownArrayHandle& ids, + const vtkm::cont::UnknownArrayHandle& values, + const vtkm::cont::UnknownArrayHandle& data, + std::false_type extractComponentInefficient); + +template +void ArraySetValuesImpl(const IdsArrayHandle& ids, + const ValuesArrayHandle& values, + const DataArrayHandle& data, + std::true_type vtkmNotUsed(extractComponentInefficient)) +{ + // Fallback implementation using control portals when device operations would be inefficient + vtkm::Id numValues = ids.GetNumberOfValues(); + VTKM_ASSERT(values.GetNumberOfValues() == numValues); + + auto idsPortal = ids.ReadPortal(); + auto valuesPortal = values.ReadPortal(); + auto dataPortal = data.WritePortal(); + + for (vtkm::Id index = 0; index < numValues; ++index) + { + dataPortal.Set(idsPortal.Get(index), valuesPortal.Get(index)); + } +} + +} // namespace internal + +/// \brief Set a small set of values in an ArrayHandle with minimal device transfers. +/// +/// The values in @a values are copied into @a data at the indices specified in @a ids. +/// This is useful for updating a subset of an array on a device without transferring +/// the entire array. +/// +/// These functions should not be called repeatedly in a loop to set all values in +/// an array handle. The much more efficient way to do this is to use the proper +/// control-side portals (ArrayHandle::WritePortal()) or to do so in a worklet. +/// +/// This method will attempt to copy the data using the device that the input +/// data is already valid on. If the input data is only valid in the control +/// environment or the device copy fails, a control-side copy is performed. +/// +/// Since a serial control-side copy may be used, this method is only intended +/// for copying small subsets of the input data. Larger subsets that would +/// benefit from parallelization should prefer using ArrayCopy with an +/// ArrayHandlePermutation. +/// +/// This utility provides several convenient overloads: +/// +/// A single id and value may be passed into ArraySetValue, or multiple ids and values +/// may be specified to ArraySetValues as ArrayHandles, std::vectors, c-arrays +/// (pointer and size), or as brace-enclosed initializer lists. +/// +/// Examples: +/// +/// ```cpp +/// vtkm::cont::ArrayHandle data = ...; +/// +/// // Set a single value in an array handle: +/// vtkm::cont::ArraySetValue(0, T{42}, data); +/// +/// // Set the first and third values in an array handle: +/// vtkm::cont::ArraySetValues({0, 2}, {T{10}, T{30}}, data); +/// +/// // Set values using std::vector +/// std::vector ids{0, 1, 2, 3}; +/// std::vector values{T{10}, T{20}, T{30}, T{40}}; +/// vtkm::cont::ArraySetValues(ids, values, data); +/// +/// // Set values using array handles directly +/// vtkm::cont::ArrayHandle idsHandle; +/// vtkm::cont::ArrayHandle valuesHandle; +/// // ... populate handles ... +/// vtkm::cont::ArraySetValues(idsHandle, valuesHandle, data); +/// +/// // Set values using raw pointers +/// vtkm::Id rawIds[] = {0, 1, 2}; +/// T rawValues[] = {T{10}, T{20}, T{30}}; +/// vtkm::cont::ArraySetValues(rawIds, 3, rawValues, 3, data); +/// ``` +/// +///@{ +/// +template +VTKM_CONT void ArraySetValues(const vtkm::cont::ArrayHandle& ids, + const vtkm::cont::ArrayHandle& values, + const vtkm::cont::ArrayHandle& data) +{ + using DataArrayHandle = vtkm::cont::ArrayHandle; + using InefficientExtract = + vtkm::cont::internal::ArrayExtractComponentIsInefficient; + internal::ArraySetValuesImpl(ids, values, data, InefficientExtract{}); +} + +/// Specialization for ArrayHandleCasts +template +VTKM_CONT void ArraySetValues( + const vtkm::cont::ArrayHandle& ids, + const vtkm::cont::ArrayHandle& values, + const vtkm::cont::ArrayHandle>& data) +{ + vtkm::cont::ArrayHandleBasic tempValues; + tempValues.Allocate(values.GetNumberOfValues()); + auto inp = values.ReadPortal(); + auto outp = tempValues.WritePortal(); + for (vtkm::Id i = 0; i < values.GetNumberOfValues(); ++i) + { + outp.Set(i, static_cast(inp.Get(i))); + } + + vtkm::cont::ArrayHandleCast> castArray = data; + ArraySetValues(ids, tempValues, castArray.GetSourceArray()); +} + +template +VTKM_CONT void ArraySetValues(const vtkm::cont::ArrayHandle& ids, + const std::vector& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto valuesAH = vtkm::cont::make_ArrayHandle(values, vtkm::CopyFlag::Off); + ArraySetValues(ids, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const std::vector& ids, + const vtkm::cont::ArrayHandle& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto idsAH = vtkm::cont::make_ArrayHandle(ids, vtkm::CopyFlag::Off); + ArraySetValues(idsAH, values, data); +} + +template +VTKM_CONT void ArraySetValues(const std::vector& ids, + const std::vector& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto idsAH = vtkm::cont::make_ArrayHandle(ids, vtkm::CopyFlag::Off); + const auto valuesAH = vtkm::cont::make_ArrayHandle(values, vtkm::CopyFlag::Off); + ArraySetValues(idsAH, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const std::initializer_list& ids, + const std::vector& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto idsAH = vtkm::cont::make_ArrayHandle( + ids.begin(), static_cast(ids.size()), vtkm::CopyFlag::Off); + const auto valuesAH = vtkm::cont::make_ArrayHandle(values, vtkm::CopyFlag::Off); + ArraySetValues(idsAH, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const std::initializer_list& ids, + const std::initializer_list& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto idsAH = vtkm::cont::make_ArrayHandle( + ids.begin(), static_cast(ids.size()), vtkm::CopyFlag::Off); + const auto valuesAH = vtkm::cont::make_ArrayHandle( + values.begin(), static_cast(values.size()), vtkm::CopyFlag::Off); + ArraySetValues(idsAH, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const std::initializer_list& ids, + const vtkm::cont::ArrayHandle& values, + const vtkm::cont::ArrayHandle& data) +{ + const auto idsAH = vtkm::cont::make_ArrayHandle( + ids.begin(), static_cast(ids.size()), vtkm::CopyFlag::Off); + ArraySetValues(idsAH, values, data); +} + +template +VTKM_CONT void ArraySetValues(const vtkm::Id* ids, + const vtkm::Id numIds, + const std::vector& values, + const vtkm::cont::ArrayHandle& data) +{ + VTKM_ASSERT(numIds == static_cast(values.size())); + const auto idsAH = vtkm::cont::make_ArrayHandle(ids, numIds, vtkm::CopyFlag::Off); + const auto valuesAH = + vtkm::cont::make_ArrayHandle(values.data(), values.size(), vtkm::CopyFlag::Off); + ArraySetValues(idsAH, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const vtkm::Id* ids, + const vtkm::Id numIds, + const T* values, + const vtkm::Id numValues, + const vtkm::cont::ArrayHandle& data) +{ + VTKM_ASSERT(numIds == numValues); + const auto idsAH = vtkm::cont::make_ArrayHandle(ids, numIds, vtkm::CopyFlag::Off); + const auto valuesAH = vtkm::cont::make_ArrayHandle(values, numValues, vtkm::CopyFlag::Off); + ArraySetValues(idsAH, valuesAH, data); +} + +template +VTKM_CONT void ArraySetValues(const vtkm::Id* ids, + const vtkm::Id numIds, + const vtkm::cont::ArrayHandle& values, + const vtkm::cont::ArrayHandle& data) +{ + VTKM_ASSERT(numIds == values.GetNumberOfValues()); + const auto idsAH = vtkm::cont::make_ArrayHandle(ids, numIds, vtkm::CopyFlag::Off); + ArraySetValues(idsAH, values, data); +} + +/// \brief Set a single value in an ArrayHandle at the specified index. +/// +/// This is a convenience function that sets a single value at the given index. +/// It is equivalent to calling ArraySetValues with single-element arrays. +/// +template +VTKM_CONT void ArraySetValue(vtkm::Id id, + const T& value, + const vtkm::cont::ArrayHandle& data) +{ + const auto idAH = vtkm::cont::make_ArrayHandle(&id, 1, vtkm::CopyFlag::Off); + const auto valueAH = vtkm::cont::make_ArrayHandle(&value, 1, vtkm::CopyFlag::Off); + ArraySetValues(idAH, valueAH, data); +} + +///@} + +} // namespace cont +} // namespace vtkm + +#endif //vtk_m_cont_ArraySetValues_h diff --git a/vtkm/cont/CMakeLists.txt b/vtkm/cont/CMakeLists.txt index 6499853705..ca47f3cbb6 100644 --- a/vtkm/cont/CMakeLists.txt +++ b/vtkm/cont/CMakeLists.txt @@ -52,6 +52,7 @@ set(headers ArrayRangeCompute.h ArrayRangeComputeTemplate.h ArrayRangeComputeTemplateInstantiationsIncludes.h + ArraySetValues.h AssignerPartitionedDataSet.h AtomicArray.h BitField.h @@ -190,6 +191,7 @@ set(device_sources ArrayHandleIndex.cxx ArrayHandleUniformPointCoordinates.cxx ArrayRangeCompute.cxx + ArraySetValues.cxx CellLocatorBoundingIntervalHierarchy.cxx CellLocatorUniformBins.cxx CellLocatorTwoLevel.cxx diff --git a/vtkm/cont/testing/CMakeLists.txt b/vtkm/cont/testing/CMakeLists.txt index 850ecd4972..250a5f787a 100644 --- a/vtkm/cont/testing/CMakeLists.txt +++ b/vtkm/cont/testing/CMakeLists.txt @@ -29,6 +29,7 @@ set(unit_tests UnitTestArrayHandleUniformPointCoordinates.cxx UnitTestArrayPortalFromIterators.cxx UnitTestArrayPortalToIterators.cxx + UnitTestArraySetValues.cxx UnitTestBuffer.cxx UnitTestComputeRange.cxx UnitTestControlSignatureTag.cxx diff --git a/vtkm/cont/testing/UnitTestArraySetValues.cxx b/vtkm/cont/testing/UnitTestArraySetValues.cxx new file mode 100644 index 0000000000..a92ee64594 --- /dev/null +++ b/vtkm/cont/testing/UnitTestArraySetValues.cxx @@ -0,0 +1,185 @@ +//============================================================================ +// 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 + +namespace +{ + +static constexpr vtkm::Id ARRAY_SIZE = 10; + +template +VTKM_CONT void TestValues(const vtkm::cont::ArrayHandle& ah, + const std::initializer_list& expected) +{ + auto portal = ah.ReadPortal(); + VTKM_TEST_ASSERT(expected.size() == static_cast(ah.GetNumberOfValues())); + for (vtkm::Id i = 0; i < ah.GetNumberOfValues(); ++i) + { + VTKM_TEST_ASSERT(expected.begin()[static_cast(i)] == portal.Get(i)); + } +} + +template +void TryCopy() +{ + std::cout << "Trying type: " << vtkm::testing::TypeName::Name() << std::endl; + + auto createData = []() -> vtkm::cont::ArrayHandle { + vtkm::cont::ArrayHandle data; + // Create and initialize the ValueType array + vtkm::cont::ArrayHandleIndex values(ARRAY_SIZE); + vtkm::cont::ArrayCopy(values, data); + return data; + }; + + { // ArrayHandle ids + const auto ids = vtkm::cont::make_ArrayHandle({ 3, 8, 7 }); + { // Pass vector + const auto data = createData(); + std::vector values{ 30, 80, 70 }; + vtkm::cont::ArraySetValues(ids, values, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + { // Pass Handle + const auto data = createData(); + const auto newValues = vtkm::cont::make_ArrayHandle({ 30, 80, 70 }); + vtkm::cont::ArraySetValues(ids, newValues, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + { // Test the specialization for ArrayHandleCast + const auto data = createData(); + auto castedData = vtkm::cont::make_ArrayHandleCast(data); + const auto doubleValues = vtkm::cont::make_ArrayHandle({ 3.0, 8.0, 7.0 }); + vtkm::cont::ArraySetValues(ids, doubleValues, castedData); + TestValues(data, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + } + } + + { // vector ids + const std::vector ids{ 3, 8, 7 }; + { // Pass vector + const auto data = createData(); + const std::vector values{ 30, 80, 70 }; + vtkm::cont::ArraySetValues(ids, values, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + { // Pass handle + const auto data = createData(); + const auto newValues = vtkm::cont::make_ArrayHandle({ 30, 80, 70 }); + vtkm::cont::ArraySetValues(ids, newValues, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + } + + { // Initializer list ids + { // Pass vector: + const auto data = createData(); + const std::vector values{ 30, 80, 70 }; + vtkm::cont::ArraySetValues({ 3, 8, 7 }, values, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); +} +{ // Pass initializer list + const auto data = createData(); + vtkm::cont::ArraySetValues( + { 3, 8, 7 }, + { static_cast(30), static_cast(80), static_cast(70) }, + data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); +} +{ // Pass handle: + const auto data = createData(); + const auto newValues = vtkm::cont::make_ArrayHandle({ 30, 80, 70 }); + vtkm::cont::ArraySetValues({ 3, 8, 7 }, newValues, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); +} +} + +{ // c-array ids + const std::vector idVec{ 3, 8, 7 }; + const vtkm::Id* ids = idVec.data(); + const auto numIds = static_cast(idVec.size()); + const std::vector valueVec{ 30, 80, 70 }; + const ValueType* values = valueVec.data(); + const auto nValues = static_cast(valueVec.size()); + { // Pass c-array + const auto data = createData(); + vtkm::cont::ArraySetValues(ids, numIds, values, nValues, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + { // Pass vector + const auto data = createData(); + vtkm::cont::ArraySetValues(ids, numIds, valueVec, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } + { // Pass Handle + const auto data = createData(); + const auto newValues = vtkm::cont::make_ArrayHandle(valueVec, vtkm::CopyFlag::Off); + vtkm::cont::ArraySetValues(ids, numIds, newValues, data); + TestValues(data, { 0, 1, 2, 30, 4, 5, 6, 70, 80, 9 }); + } +} + +{ // single value + const auto data = createData(); + vtkm::cont::ArraySetValue(8, static_cast(88), data); + TestValues(data, { 0, 1, 2, 3, 4, 5, 6, 7, 88, 9 }); +} +} + +void TryRange() +{ + std::cout << "Trying vtkm::Range" << std::endl; + + vtkm::cont::ArrayHandle values = + vtkm::cont::make_ArrayHandle({ { 0.0, 1.0 }, { 1.0, 2.0 }, { 2.0, 4.0 } }); + + vtkm::cont::ArraySetValue(1, vtkm::Range{ 5.0, 6.0 }, values); + auto portal = values.ReadPortal(); + VTKM_TEST_ASSERT(portal.Get(1) == vtkm::Range{ 5.0, 6.0 }); +} + +void TryBounds() +{ + std::cout << "Trying vtkm::Bounds" << std::endl; + + vtkm::cont::ArrayHandle values = + vtkm::cont::make_ArrayHandle({ { { 0.0, 1.0 }, { 0.0, 1.0 }, { 0.0, 1.0 } }, + { { 1.0, 2.0 }, { 1.0, 2.0 }, { 1.0, 2.0 } }, + { { 2.0, 4.0 }, { 2.0, 4.0 }, { 2.0, 4.0 } } }); + + vtkm::cont::ArraySetValue(1, vtkm::Bounds{ { 5.0, 6.0 }, { 5.0, 6.0 }, { 5.0, 6.0 } }, values); + auto portal = values.ReadPortal(); + VTKM_TEST_ASSERT(portal.Get(1) == vtkm::Bounds{ { 5.0, 6.0 }, { 5.0, 6.0 }, { 5.0, 6.0 } }); +} + +void Test() +{ + TryCopy(); + TryCopy(); + TryCopy(); + TryRange(); + TryBounds(); +} + +} // anonymous namespace + +int UnitTestArraySetValues(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(Test, argc, argv); +} -- GitLab From d641d2766eeed0b672683fec4ecfa8b0340834d5 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Mon, 3 Feb 2025 14:28:01 -0500 Subject: [PATCH 2/2] Add changelog --- docs/changelog/add-array-set-values.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 docs/changelog/add-array-set-values.md diff --git a/docs/changelog/add-array-set-values.md b/docs/changelog/add-array-set-values.md new file mode 100644 index 0000000000..fc837e57dd --- /dev/null +++ b/docs/changelog/add-array-set-values.md @@ -0,0 +1,4 @@ +## Add ArraySetValues + +ArraySetValues.h is a new header file that includes functions to set 1 or many values in an array +similarly to how ArrayGetValues.h gets 1 or many values from an array. -- GitLab