/**************************************************************************** * Copyright (c) 2025, ArborX authors * * All rights reserved. * * * * This file is part of the ArborX library. ArborX is * * distributed under a BSD 3-clause license. For the licensing terms see * * the LICENSE file in the top-level directory. * * * * SPDX-License-Identifier: BSD-3-Clause * ****************************************************************************/ #include #include #include #include "Search_UnitTestHelpers.hpp" // clang-format off #include "ArborXTest_TreeTypeTraits.hpp" // clang-format on BOOST_AUTO_TEST_SUITE(Callbacks) namespace tt = boost::test_tools; template struct CustomInlineCallback { static_assert(Kokkos::is_view_v && Points::rank() == 1); using Point = typename Points::value_type; Points points; Point const origin = {{0., 0., 0.}}; template KOKKOS_FUNCTION void operator()(Query const &, int index, Insert const &insert) const { auto const distance_to_origin = ArborX::distance(points(index), origin); insert({index, distance_to_origin}); } }; template struct CustomPostCallback { static_assert(Kokkos::is_view_v && Points::rank() == 1); using Point = typename Points::value_type; using tag = ArborX::Details::PostCallbackTag; Points points; Point const origin = {{0., 0., 0.}}; template void operator()(Predicates const &, InOutView &offset, InView in, OutView &out) const { using ExecutionSpace = typename Points::execution_space; auto const n = offset.extent(0) - 1; Kokkos::realloc(out, in.extent(0)); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_CLASS_LAMBDA(int i) { for (int j = offset(i); j < offset(i + 1); ++j) out(j) = {in(j), distance(points(in(j)), origin)}; }); } }; template std::vector>> initialize_values(Points const &points, float const delta) { using MemorySpace = typename Points::memory_space; using ExecutionSpace = typename Points::execution_space; using Coordinate = ArborX::GeometryTraits::coordinate_type_t; using PairIndexDistance = Kokkos::pair; int const n = points.size(); Kokkos::View values_device("values_device", n); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_LAMBDA(int i) { ArborX::Point const origin{(Coordinate)0, (Coordinate)0, (Coordinate)0}; values_device(i) = {i, delta + ArborX::distance(points(i), origin)}; }); std::vector values(n); Kokkos::deep_copy( Kokkos::View(values.data(), n), values_device); return values; } BOOST_AUTO_TEST_CASE_TEMPLATE(callback_spatial_predicate, TreeTypeTraits, TreeTypeTraitsList) { using Tree = typename TreeTypeTraits::type; using ExecutionSpace = typename TreeTypeTraits::execution_space; using DeviceType = typename TreeTypeTraits::device_type; using Coordinate = ArborX::GeometryTraits::coordinate_type_t< typename Tree::bounding_volume_type>; using Point = ArborX::Point<3, Coordinate>; using Box = ArborX::Box<3, Coordinate>; int const n = 10; Kokkos::View points( Kokkos::view_alloc(Kokkos::WithoutInitializing, "points"), n); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_LAMBDA(int i) { points(i) = {{(Coordinate)i, (Coordinate)i, (Coordinate)i}}; }); auto values = initialize_values(points, /*delta*/ 0.f); std::vector offsets = {0, n}; Tree const tree(ExecutionSpace{}, points); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeIntersectsQueries({ static_cast(tree.bounds()), })), CustomInlineCallback{points}, make_compressed_storage(offsets, values)); ARBORX_TEST_QUERY_TREE_CALLBACK(ExecutionSpace{}, tree, (makeIntersectsQueries({ static_cast(tree.bounds()), })), CustomPostCallback{points}, make_compressed_storage(offsets, values)); } #ifndef ARBORX_TEST_DISABLE_NEAREST_QUERY BOOST_AUTO_TEST_CASE_TEMPLATE(callback_nearest_predicate, TreeTypeTraits, TreeTypeTraitsList) { using Tree = typename TreeTypeTraits::type; using ExecutionSpace = typename TreeTypeTraits::execution_space; using DeviceType = typename TreeTypeTraits::device_type; using Coordinate = ArborX::GeometryTraits::coordinate_type_t< typename Tree::bounding_volume_type>; using Point = ArborX::Point<3, Coordinate>; int const n = 10; Kokkos::View points( Kokkos::view_alloc(Kokkos::WithoutInitializing, "points"), n); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_LAMBDA(int i) { points(i) = {{(Coordinate)i, (Coordinate)i, (Coordinate)i}}; }); #ifdef KOKKOS_COMPILER_NVCC [[maybe_unused]] #endif Point const origin = {{0., 0., 0.}}; auto values = initialize_values(points, /*delta*/ 0.f); std::vector offsets = {0, n}; Tree const tree(ExecutionSpace{}, points); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeNearestQueries({ {origin, n}, })), CustomInlineCallback{points}, make_compressed_storage(offsets, values)); ARBORX_TEST_QUERY_TREE_CALLBACK(ExecutionSpace{}, tree, (makeNearestQueries({ {origin, n}, })), CustomPostCallback{points}, make_compressed_storage(offsets, values)); } #endif #ifndef ARBORX_TEST_DISABLE_CALLBACK_EARLY_EXIT template struct Experimental_CustomCallbackEarlyExit { Kokkos::View> counts; template KOKKOS_FUNCTION auto operator()(Predicate const &predicate, Value const &) const { int i = getData(predicate); if (counts(i)++ < i) { return ArborX::CallbackTreeTraversalControl::normal_continuation; } return ArborX::CallbackTreeTraversalControl::early_exit; } }; BOOST_AUTO_TEST_CASE_TEMPLATE(callback_early_exit, TreeTypeTraits, TreeTypeTraitsList) { using Tree = typename TreeTypeTraits::type; using ExecutionSpace = typename TreeTypeTraits::execution_space; using DeviceType = typename TreeTypeTraits::device_type; using BoundingVolume = typename Tree::bounding_volume_type; constexpr int DIM = ArborX::GeometryTraits::dimension_v; using Coordinate = ArborX::GeometryTraits::coordinate_type_t; using Box = ArborX::Box; auto const tree = make(ExecutionSpace{}, { {{{0., 0., 0.}}, {{0., 0., 0.}}}, {{{1., 1., 1.}}, {{1., 1., 1.}}}, {{{2., 2., 2.}}, {{2., 2., 2.}}}, {{{3., 3., 3.}}, {{3., 3., 3.}}}, }); Kokkos::View counts("counts", 4); std::vector counts_ref(4); std::iota(counts_ref.begin(), counts_ref.end(), 1); Box b; ArborX::Details::expand(b, tree.bounds()); auto predicates = makeIntersectsWithAttachmentQueries( {b, b, b, b}, {0, 1, 2, 3}); tree.query(ExecutionSpace{}, predicates, Experimental_CustomCallbackEarlyExit{counts}); auto counts_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, counts); BOOST_TEST(counts_host == counts_ref, tt::per_element()); } #endif template struct CustomInlineCallbackWithAttachment { using Point = typename Points::value_type; Points points; Point const origin = {{0., 0., 0.}}; template KOKKOS_FUNCTION void operator()(Query const &query, int index, Insert const &insert) const { auto const distance_to_origin = ArborX::distance(points(index), origin); auto data = ArborX::getData(query); insert({index, data + distance_to_origin}); } }; template struct CustomPostCallbackWithAttachment { using tag = ArborX::Details::PostCallbackTag; using Point = typename Points::value_type; Points points; Point const origin = {{0., 0., 0.}}; template void operator()(Predicates const &queries, InOutView &offset, InView in, OutView &out) const { using ExecutionSpace = typename Points::execution_space; auto const n = offset.extent(0) - 1; Kokkos::realloc(out, in.extent(0)); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_CLASS_LAMBDA(int i) { auto data_2 = ArborX::getData(queries(i)); auto data = data_2[1]; for (int j = offset(i); j < offset(i + 1); ++j) { out(j) = {in(j), data + distance(points(in(j)), origin)}; } }); } }; BOOST_AUTO_TEST_CASE_TEMPLATE(callback_with_attachment_spatial_predicate, TreeTypeTraits, TreeTypeTraitsList) { using Tree = typename TreeTypeTraits::type; using ExecutionSpace = typename TreeTypeTraits::execution_space; using DeviceType = typename TreeTypeTraits::device_type; using BoundingVolume = typename Tree::bounding_volume_type; using Coordinate = ArborX::GeometryTraits::coordinate_type_t; using Point = ArborX::Point<3, Coordinate>; using Box = ArborX::Box<3, Coordinate>; int const n = 10; Kokkos::View points( Kokkos::view_alloc(Kokkos::WithoutInitializing, "points"), n); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_LAMBDA(int i) { points(i) = {{(Coordinate)i, (Coordinate)i, (Coordinate)i}}; }); Coordinate const delta = 5.f; auto values = initialize_values(points, delta); std::vector offsets = {0, n}; Tree const tree(ExecutionSpace{}, points); Box bounds; ArborX::Details::expand(bounds, tree.bounds()); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeIntersectsWithAttachmentQueries( {bounds}, {delta})), CustomInlineCallbackWithAttachment{points}, make_compressed_storage(offsets, values)); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeIntersectsWithAttachmentQueries>( {bounds}, {{0., delta}})), CustomPostCallbackWithAttachment{points}, make_compressed_storage(offsets, values)); } #ifndef ARBORX_TEST_DISABLE_NEAREST_QUERY BOOST_AUTO_TEST_CASE_TEMPLATE(callback_with_attachment_nearest_predicate, TreeTypeTraits, TreeTypeTraitsList) { using Tree = typename TreeTypeTraits::type; using ExecutionSpace = typename TreeTypeTraits::execution_space; using DeviceType = typename TreeTypeTraits::device_type; using BoundingVolume = typename Tree::bounding_volume_type; using Coordinate = ArborX::GeometryTraits::coordinate_type_t; using Point = ArborX::Point<3, Coordinate>; int const n = 10; Kokkos::View points( Kokkos::view_alloc(Kokkos::WithoutInitializing, "points"), n); Kokkos::parallel_for( Kokkos::RangePolicy(0, n), KOKKOS_LAMBDA(int i) { points(i) = {{(Coordinate)i, (Coordinate)i, (Coordinate)i}}; }); #ifdef KOKKOS_COMPILER_NVCC [[maybe_unused]] #endif Coordinate const delta = 5.f; Point const origin = {{0., 0., 0.}}; auto values = initialize_values(points, delta); std::vector offsets = {0, n}; Tree const tree(ExecutionSpace{}, points); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeNearestWithAttachmentQueries( {{origin, n}}, {delta})), CustomInlineCallbackWithAttachment{points}, make_compressed_storage(offsets, values)); ARBORX_TEST_QUERY_TREE_CALLBACK( ExecutionSpace{}, tree, (makeNearestWithAttachmentQueries>( {{origin, n}}, {{0, delta}})), CustomPostCallbackWithAttachment{points}, make_compressed_storage(offsets, values)); } #endif BOOST_AUTO_TEST_SUITE_END()