/**************************************************************************** * Copyright (c) 2017-2022 by the 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 * ****************************************************************************/ #ifndef ARBORX_BOOST_RTREE_HELPERS_HPP #define ARBORX_BOOST_RTREE_HELPERS_HPP #include #include "ArborX_BoostGeometryAdapters.hpp" #include "ArborX_BoostRangeAdapters.hpp" #include #include // is_accessible_from_host #include // exclusive_scan #include // lastElement #include #include #include #ifdef ARBORX_ENABLE_MPI #include #endif #include #include #include #include #ifdef ARBORX_ENABLE_MPI #include #endif namespace BoostRTreeHelpers { // NOTE: The balancing algorithm does not really matter since the tree is // created using packing algorithm and there is no later insertion of new // objects into the tree. The values of the maximum and the minimum number // of elements could be adjusted in principle but I didn't bother setting // this type as a template argument. using Parameter = boost::geometry::index::linear<16>; template struct PairMaker { using result_type = std::pair; template inline result_type operator()(T const &v) const { return result_type(v.value(), v.index()); } }; template using RTree = boost::geometry::index::rtree, Parameter>; template static RTree makeRTree(View const &objects) { using Indexable = typename View::value_type; return RTree( objects | boost::adaptors::indexed() | boost::adaptors::transformed(PairMaker())); } // NOTE: Boost.Config defines BOOST_NO_CXX11_VARIADIC_TEMPLATES for nvcc // with the current version of CUDA we are using. In consequence we are // not able to use std::tuple which is unfortunate :( class AppendRankToPairObjectIndex { public: AppendRankToPairObjectIndex(int rank) : _rank(rank) {} template inline boost::tuple operator()(std::pair const &p) const { return boost::make_tuple(std::get<0>(p), std::get<1>(p), _rank); } private: int _rank = -1; }; #ifdef ARBORX_ENABLE_MPI template using ParallelRTree = boost::geometry::index::rtree, Parameter>; template static ParallelRTree makeRTree(MPI_Comm comm, View const &objects) { using Indexable = typename View::value_type; // Fill buffer with pair (object, index) std::vector> buffer; boost::copy(objects | boost::adaptors::indexed() | boost::adaptors::transformed(PairMaker()), std::back_inserter(buffer)); // Gather all buffers int comm_size; MPI_Comm_size(comm, &comm_size); int comm_rank; MPI_Comm_rank(comm, &comm_rank); std::vector counts(comm_size); counts[comm_rank] = buffer.size(); MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, counts.data(), 1, MPI_INT, comm); std::vector offsets(comm_size + 1); offsets[0] = 0; for (int i = 0; i < comm_size; ++i) offsets[i + 1] = counts[i] + offsets[i]; decltype(buffer) all_buffers(offsets.back()); auto const bytes_per_element = sizeof(typename decltype(buffer)::value_type); for (auto *pv : {&counts, &offsets}) for (auto &x : *pv) x *= bytes_per_element; MPI_Allgatherv(buffer.data(), counts[comm_rank], MPI_BYTE, all_buffers.data(), counts.data(), offsets.data(), MPI_BYTE, comm); for (auto &x : offsets) x /= bytes_per_element; std::vector> all_objects; for (int i = 0; i < comm_size; ++i) boost::transform(std::make_pair(all_buffers.data() + offsets[i], all_buffers.data() + offsets[i + 1]), std::back_inserter(all_objects), AppendRankToPairObjectIndex(i)); return ParallelRTree(all_objects); } #endif template struct UnaryPredicate { using Function = std::function; UnaryPredicate(Function pred) : _pred(pred) {} inline bool operator()(Value const &val) const { return _pred(val); } Function _pred; }; template static auto translate(ArborX::Intersects const &query) { auto const sphere = getGeometry(query); auto const radius = sphere.radius(); auto const centroid = sphere.centroid(); ArborX::Box box; ArborX::Details::expand(box, sphere); return boost::geometry::index::intersects(box) && boost::geometry::index::satisfies( UnaryPredicate([centroid, radius](Value const &val) { boost::geometry::index::indexable indexableGetter; auto const &geometry = indexableGetter(val); return boost::geometry::distance(centroid, geometry) <= radius; })); } template static auto translate(ArborX::Intersects const &query) { ArborX::Box const &box = getGeometry(query); return boost::geometry::index::intersects(box); } template static auto translate(ArborX::Nearest const &query) { auto const geometry = getGeometry(query); auto const k = getK(query); return boost::geometry::index::nearest(geometry, k); } template > static std::tuple performQueries(RTree const &rtree, InputView const &queries) { namespace KokkosExt = ArborX::Details::KokkosExt; static_assert(KokkosExt::is_accessible_from_host::value); using Value = typename RTree::value_type; auto const n_queries = queries.extent_int(0); OutputView offset("offset", n_queries + 1); std::vector returned_values; for (int i = 0; i < n_queries; ++i) offset(i) = rtree.query(translate(queries(i)), std::back_inserter(returned_values)); using ExecutionSpace = typename InputView::execution_space; ExecutionSpace space; KokkosExt::exclusive_scan(space, offset, offset, 0); auto const n_results = KokkosExt::lastElement(space, offset); OutputView indices("indices", n_results); for (int i = 0; i < n_queries; ++i) for (int j = offset(i); j < offset(i + 1); ++j) indices(j) = returned_values[j].second; return std::make_tuple(offset, indices); } #ifdef ARBORX_ENABLE_MPI template , typename OutputView2 = Kokkos::View> static std::tuple performQueries(ParallelRTree const &rtree, InputView const &queries) { namespace KokkosExt = ArborX::Details::KokkosExt; static_assert(KokkosExt::is_accessible_from_host::value); using Value = typename ParallelRTree::value_type; auto const n_queries = queries.extent_int(0); OutputView2 offset("offset", n_queries + 1); std::vector returned_values; for (int i = 0; i < n_queries; ++i) offset(i) = rtree.query(translate(queries(i)), std::back_inserter(returned_values)); using ExecutionSpace = typename InputView::execution_space; ExecutionSpace space; KokkosExt::exclusive_scan(space, offset, offset, 0); auto const n_results = KokkosExt::lastElement(space, offset); OutputView1 values("values", n_results); for (int i = 0; i < n_queries; ++i) for (int j = offset(i); j < offset(i + 1); ++j) { int index; int rank; boost::tie(boost::tuples::ignore, index, rank) = returned_values[j]; values(j) = {index, rank}; } return std::make_tuple(offset, values); } #endif } // end namespace BoostRTreeHelpers namespace BoostExt { // FIXME Goal is to match the BVH interface template class RTree { public: using DeviceType = Kokkos::DefaultHostExecutionSpace::device_type; using memory_space = typename DeviceType::memory_space; template RTree(ExecutionSpace, Kokkos::View const &values) { static_assert(Kokkos::is_execution_space::value); _tree = BoostRTreeHelpers::makeRTree(values); } // WARNING trailing pack will match anything :/ template void query(ExecutionSpace const &, Predicates const &predicates, InputView &indices, InputView &offset, TrailingArgs &&...) const { static_assert(Kokkos::is_execution_space::value); std::tie(offset, indices) = BoostRTreeHelpers::performQueries(_tree, predicates); } template void query(ExecutionSpace const &, Predicates const &, Callback const &, TrailingArgs &&...) const { static_assert(Kokkos::is_execution_space::value); throw std::runtime_error( "Boost RTree does not support callback only query overload."); } private: BoostRTreeHelpers::RTree _tree; }; #ifdef ARBORX_ENABLE_MPI template class ParallelRTree { public: using DeviceType = Kokkos::DefaultHostExecutionSpace::device_type; using memory_space = typename DeviceType::memory_space; template ParallelRTree(MPI_Comm comm, ExecutionSpace const &, Kokkos::View const &values) { static_assert(Kokkos::is_execution_space::value); _tree = BoostRTreeHelpers::makeRTree(comm, values); } // WARNING trailing pack will match anything :/ template void query(ExecutionSpace const &, Predicates const &predicates, InputView1 &indices, InputView2 &offset, TrailingArgs &&...) const { static_assert(Kokkos::is_execution_space::value); std::tie(offset, indices) = BoostRTreeHelpers::performQueries(_tree, predicates); } private: BoostRTreeHelpers::ParallelRTree _tree; }; #endif } // namespace BoostExt namespace ArborX { // Specialization of ArborX::query template inline void query(BoostExt::RTree const &rtree, ExecutionSpace const &space, Predicates const &predicates, InputView &indices, InputView &offset, TrailingArgs &&...args) { rtree.query(space, predicates, indices, offset, std::forward(args)...); } } // namespace ArborX #endif