-
Notifications
You must be signed in to change notification settings - Fork 156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Header Only API for Linestring-Polygon Distance #1011
Merged
rapids-bot
merged 42 commits into
rapidsai:branch-23.04
from
isVoid:feature/linestring_polygon_distance_header
Apr 6, 2023
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
7022c1d
initial
isVoid 5686be3
add segment iterator and tests
isVoid a74af14
WIP
isVoid 1c28faa
making progress towards getting the right index
isVoid 243d2d9
commiting progress
isVoid 0da0afc
able to get the right segment combinations from within the kernel, ne…
isVoid 49e43aa
[skip-ci] initial port from feature/linestring_polygon_distance_header
isVoid e8e45d0
Apply suggestions from code review
isVoid fadf5c8
Update cpp/include/cuspatial/experimental/detail/functors.cuh
isVoid fe31542
address review comments
isVoid af0c35e
address review comments
isVoid 05bf7ff
[skip-ci] address review coments
isVoid 2435126
Add docstrings for functors
isVoid 98a4a0f
[skip-ci] adds linestring_count test
isVoid 9b72723
[skip-ci] revert removed code
isVoid 6971200
style
isVoid 6f7a226
update unexpected changes
isVoid 641934f
license
isVoid c580876
Merge branch 'feature/segment_iterators' into feature/linestring_poly…
isVoid 890e95b
Update cpp/include/cuspatial/experimental/detail/ranges/multilinestri…
isVoid 040e432
making a bit of progress
isVoid 7c3603c
fix broken multilinestring->multipoint constructor
isVoid 5966ad0
Merge branch 'feature/segment_iterators' of github.com:isVoid/cuspati…
isVoid 70ca1b6
fix broken multilinestring->multipoint constructor
isVoid 9205533
passing existing tests
isVoid 4bb3ca3
fix wrong test setup and update docs
isVoid 9f34700
Merge branch 'feature/segment_iterators' into feature/linestring_poly…
isVoid 8f20feb
style
isVoid 3ffe5ee
Delete point_in_multipolygon.cuh
isVoid 894f870
Delete linestring_polygon_distance.hpp
isVoid 2b99555
cleanup - first round
isVoid 656d093
Merge branch 'feature/linestring_polygon_distance_header' of github.c…
isVoid 62d821f
merge duplicate code
isVoid 7d22749
add missing header
isVoid 9051944
style
isVoid e082aa1
fix OB access error
isVoid d94e060
address reviews
isVoid 5dedc52
address reviews
isVoid 314ad04
address review comments
isVoid 26f272c
Merge branch 'branch-23.04' of https://github.com/rapidsai/cuspatial …
isVoid 75a8fd2
style
isVoid dd1b09c
fix indirect dependency on thrust includes
isVoid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
115 changes: 115 additions & 0 deletions
115
cpp/include/cuspatial/experimental/detail/distance_utils.cuh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright (c) 2023, NVIDIA CORPORATION. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#include <cuspatial/detail/utility/zero_data.cuh> | ||
#include <cuspatial/experimental/iterator_factory.cuh> | ||
#include <cuspatial/traits.hpp> | ||
#include <cuspatial/vec_2d.hpp> | ||
|
||
#include <rmm/cuda_stream_view.hpp> | ||
#include <rmm/device_uvector.hpp> | ||
#include <rmm/exec_policy.hpp> | ||
|
||
#include <thrust/iterator/discard_iterator.h> | ||
#include <thrust/logical.h> | ||
#include <thrust/tabulate.h> | ||
|
||
namespace cuspatial { | ||
namespace detail { | ||
|
||
/** | ||
* @brief For each point in the multipoint, compute point-in-multipolygon in corresponding pair. | ||
*/ | ||
template <typename MultiPointRange, typename MultiPolygonRange> | ||
struct point_in_multipolygon_test_functor { | ||
using T = typename MultiPointRange::element_t; | ||
|
||
MultiPointRange multipoints; | ||
MultiPolygonRange multipolygons; | ||
|
||
point_in_multipolygon_test_functor(MultiPointRange multipoints, MultiPolygonRange multipolygons) | ||
: multipoints(multipoints), multipolygons(multipolygons) | ||
{ | ||
} | ||
|
||
template <typename IndexType> | ||
uint8_t __device__ operator()(IndexType pidx) | ||
{ | ||
vec_2d<T> const& point = multipoints.point(pidx); | ||
auto geometry_idx = multipoints.geometry_idx_from_point_idx(pidx); | ||
|
||
auto const& polys = multipolygons[geometry_idx]; | ||
|
||
// TODO: benchmark against range based for loop | ||
bool intersects = | ||
thrust::any_of(thrust::seq, polys.begin(), polys.end(), [&point] __device__(auto poly) { | ||
return is_point_in_polygon(point, poly); | ||
}); | ||
|
||
return static_cast<uint8_t>(intersects); | ||
} | ||
}; | ||
|
||
/** | ||
* @brief Compute whether each multipoint intersects with the corresponding multipolygon. | ||
* | ||
* First, compute the point-multipolygon intersection. Then use reduce-by-key to | ||
* compute the multipoint-multipolygon intersection. | ||
* Caveat: may have load unbalanced kernel if input is skewed with non-uniform distribution with the | ||
* number of polygons in multipolygon. | ||
* | ||
* @tparam MultiPointRange An instantiation of multipoint_range | ||
* @tparam MultiPolygonRange An instantiation of multipolygon_range | ||
* @param multipoints The range to the multipoints to compute | ||
* @param multipolygons The range to the multipolygons to test | ||
* @param stream The CUDA stream on which to perform computations | ||
* @return A uint8_t array, `1` if the multipoint intersects with the multipolygon, `0` otherwise. | ||
*/ | ||
template <typename MultiPointRange, typename MultiPolygonRange> | ||
rmm::device_uvector<uint8_t> point_polygon_intersects(MultiPointRange multipoints, | ||
MultiPolygonRange multipolygons, | ||
rmm::cuda_stream_view stream) | ||
{ | ||
using index_t = iterator_value_type<typename MultiPointRange::geometry_it_t>; | ||
rmm::device_uvector<uint8_t> point_intersects(multipoints.num_points(), stream); | ||
|
||
thrust::tabulate(rmm::exec_policy(stream), | ||
point_intersects.begin(), | ||
point_intersects.end(), | ||
detail::point_in_multipolygon_test_functor{multipoints, multipolygons}); | ||
|
||
// `multipoints` contains only single points, no need to reduce. | ||
if (multipoints.is_single_point_range()) return point_intersects; | ||
|
||
rmm::device_uvector<uint8_t> multipoint_intersects(multipoints.num_multipoints(), stream); | ||
detail::zero_data_async(multipoint_intersects.begin(), multipoint_intersects.end(), stream); | ||
|
||
auto offset_as_key_it = | ||
make_geometry_id_iterator<index_t>(multipoints.offsets_begin(), multipoints.offsets_end()); | ||
|
||
thrust::reduce_by_key(rmm::exec_policy(stream), | ||
offset_as_key_it, | ||
offset_as_key_it + multipoints.num_points(), | ||
point_intersects.begin(), | ||
thrust::make_discard_iterator(), | ||
multipoint_intersects.begin(), | ||
thrust::logical_or<uint8_t>()); | ||
|
||
return multipoint_intersects; | ||
} | ||
|
||
} // namespace detail | ||
} // namespace cuspatial |
190 changes: 190 additions & 0 deletions
190
cpp/include/cuspatial/experimental/detail/linestring_polygon_distance.cuh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/* | ||
* Copyright (c) 2023, NVIDIA CORPORATION. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "distance_utils.cuh" | ||
|
||
#include <cuspatial/cuda_utils.hpp> | ||
#include <cuspatial/detail/utility/device_atomics.cuh> | ||
#include <cuspatial/detail/utility/linestring.cuh> | ||
#include <cuspatial/detail/utility/zero_data.cuh> | ||
#include <cuspatial/experimental/detail/algorithm/is_point_in_polygon.cuh> | ||
#include <cuspatial/experimental/detail/functors.cuh> | ||
#include <cuspatial/experimental/iterator_factory.cuh> | ||
#include <cuspatial/experimental/ranges/range.cuh> | ||
#include <cuspatial/vec_2d.hpp> | ||
|
||
#include <thrust/fill.h> | ||
#include <thrust/functional.h> | ||
#include <thrust/iterator/discard_iterator.h> | ||
#include <thrust/iterator/permutation_iterator.h> | ||
#include <thrust/logical.h> | ||
#include <thrust/scan.h> | ||
#include <thrust/zip_function.h> | ||
|
||
#include <rmm/cuda_stream_view.hpp> | ||
#include <rmm/device_uvector.hpp> | ||
#include <rmm/exec_policy.hpp> | ||
|
||
#include <limits> | ||
|
||
namespace cuspatial { | ||
namespace detail { | ||
|
||
/** | ||
* @brief Computes distances between the multilinestring and multipolygons | ||
* | ||
* @param multilinestrings Range to the multilinestring | ||
* @param multipolygons Range to the multipolygon | ||
* @param thread_bounds Range to the boundary of thread partitions | ||
* @param multilinestrings_segment_offsets Range to the indices where the first segment of each | ||
* multilinestring begins | ||
* @param multipolygons_segment_offsets Range to the indices where the first segment of each | ||
* multipolygon begins | ||
* @param intersects A uint8_t array that indicates if the corresponding pair of multipoint and | ||
* multipolygon intersects | ||
* @param distances Output range of distances, pre-filled with std::numerical_limits<T>::max() | ||
*/ | ||
template <typename MultiLinestringRange, | ||
typename MultiPolygonRange, | ||
typename IndexRange, | ||
typename OutputIt> | ||
void __global__ | ||
pairwise_linestring_polygon_distance_kernel(MultiLinestringRange multilinestrings, | ||
MultiPolygonRange multipolygons, | ||
IndexRange thread_bounds, | ||
IndexRange multilinestrings_segment_offsets, | ||
IndexRange multipolygons_segment_offsets, | ||
uint8_t* intersects, | ||
OutputIt* distances) | ||
{ | ||
using T = typename MultiLinestringRange::element_t; | ||
using index_t = iterator_value_type<typename MultiLinestringRange::geometry_it_t>; | ||
|
||
auto num_threads = thread_bounds[thread_bounds.size() - 1]; | ||
for (auto idx = blockDim.x * blockIdx.x + threadIdx.x; idx < num_threads; | ||
idx += blockDim.x * gridDim.x) { | ||
auto it = thrust::prev( | ||
thrust::upper_bound(thrust::seq, thread_bounds.begin(), thread_bounds.end(), idx)); | ||
auto geometry_id = thrust::distance(thread_bounds.begin(), it); | ||
auto local_idx = idx - *it; | ||
|
||
if (intersects[geometry_id]) { | ||
distances[geometry_id] = 0.0f; | ||
continue; | ||
} | ||
|
||
// Retrieve the number of segments in multilinestrings[geometry_id] | ||
auto num_segment_this_multilinestring = | ||
multilinestrings.multilinestring_segment_count_begin()[geometry_id]; | ||
// The segment id from the multilinestring this thread is computing (local_id + global_offset) | ||
auto multilinestring_segment_id = | ||
local_idx % num_segment_this_multilinestring + multilinestrings_segment_offsets[geometry_id]; | ||
// The segment id from the multipolygon this thread is computing (local_id + global_offset) | ||
auto multipolygon_segment_id = | ||
local_idx / num_segment_this_multilinestring + multipolygons_segment_offsets[geometry_id]; | ||
|
||
auto [a, b] = multilinestrings.segment_begin()[multilinestring_segment_id]; | ||
auto [c, d] = multipolygons.segment_begin()[multipolygon_segment_id]; | ||
|
||
atomicMin(&distances[geometry_id], sqrt(squared_segment_distance(a, b, c, d))); | ||
} | ||
}; | ||
|
||
} // namespace detail | ||
|
||
template <class MultiLinestringRange, class MultiPolygonRange, class OutputIt> | ||
OutputIt pairwise_linestring_polygon_distance(MultiLinestringRange multilinestrings, | ||
MultiPolygonRange multipolygons, | ||
OutputIt distances_first, | ||
rmm::cuda_stream_view stream) | ||
{ | ||
using T = typename MultiLinestringRange::element_t; | ||
using index_t = iterator_value_type<typename MultiLinestringRange::geometry_it_t>; | ||
|
||
CUSPATIAL_EXPECTS(multilinestrings.size() == multipolygons.size(), | ||
"Must have the same number of input rows."); | ||
|
||
if (multilinestrings.size() == 0) return distances_first; | ||
|
||
// Create a multipoint range from multilinestrings, computes intersection | ||
auto multipoints = multilinestrings.as_multipoint_range(); | ||
auto multipoint_intersects = point_polygon_intersects(multipoints, multipolygons, stream); | ||
|
||
// Compute the "boundary" of threads. Threads are partitioned based on the number of linestrings | ||
// times the number of polygons in a multipoint-multipolygon pair. | ||
auto segment_count_product_it = thrust::make_transform_iterator( | ||
thrust::make_zip_iterator(multilinestrings.multilinestring_segment_count_begin(), | ||
multipolygons.multipolygon_segment_count_begin()), | ||
thrust::make_zip_function(thrust::multiplies<index_t>{})); | ||
|
||
// Computes the "thread boundary" of each pair. This array partitions the thread range by | ||
// geometries. E.g. threadIdx within [thread_bounds[i], thread_bounds[i+1]) computes distances of | ||
// the ith pair. | ||
auto thread_bounds = rmm::device_uvector<index_t>(multilinestrings.size() + 1, stream); | ||
detail::zero_data_async(thread_bounds.begin(), thread_bounds.end(), stream); | ||
|
||
thrust::inclusive_scan(rmm::exec_policy(stream), | ||
segment_count_product_it, | ||
segment_count_product_it + thread_bounds.size() - 1, | ||
thrust::next(thread_bounds.begin())); | ||
|
||
// Compute offsets to the first segment of each multilinestring and multipolygon | ||
auto multilinestring_segment_offsets = | ||
rmm::device_uvector<index_t>(multilinestrings.num_multilinestrings() + 1, stream); | ||
detail::zero_data_async( | ||
multilinestring_segment_offsets.begin(), multilinestring_segment_offsets.end(), stream); | ||
|
||
auto multipolygon_segment_offsets = | ||
rmm::device_uvector<index_t>(multipolygons.num_multipolygons() + 1, stream); | ||
detail::zero_data_async( | ||
multipolygon_segment_offsets.begin(), multipolygon_segment_offsets.end(), stream); | ||
|
||
thrust::inclusive_scan(rmm::exec_policy(stream), | ||
multilinestrings.multilinestring_segment_count_begin(), | ||
multilinestrings.multilinestring_segment_count_begin() + | ||
multilinestrings.num_multilinestrings(), | ||
thrust::next(multilinestring_segment_offsets.begin())); | ||
|
||
thrust::inclusive_scan( | ||
rmm::exec_policy(stream), | ||
multipolygons.multipolygon_segment_count_begin(), | ||
multipolygons.multipolygon_segment_count_begin() + multipolygons.num_multipolygons(), | ||
thrust::next(multipolygon_segment_offsets.begin())); | ||
|
||
// Initialize output range | ||
thrust::fill(rmm::exec_policy(stream), | ||
distances_first, | ||
distances_first + multilinestrings.num_multilinestrings(), | ||
std::numeric_limits<T>::max()); | ||
|
||
auto num_threads = thread_bounds.back_element(stream); | ||
auto [tpb, num_blocks] = grid_1d(num_threads); | ||
|
||
detail::pairwise_linestring_polygon_distance_kernel<<<num_blocks, tpb, 0, stream.value()>>>( | ||
multilinestrings, | ||
multipolygons, | ||
range{thread_bounds.begin(), thread_bounds.end()}, | ||
range{multilinestring_segment_offsets.begin(), multilinestring_segment_offsets.end()}, | ||
range{multipolygon_segment_offsets.begin(), multipolygon_segment_offsets.end()}, | ||
multipoint_intersects.begin(), | ||
distances_first); | ||
|
||
return distances_first + multilinestrings.num_multilinestrings(); | ||
} | ||
|
||
} // namespace cuspatial |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've probably already thought of this but I see you wanting to have the thread partitioning code here and above refactored into a utility space for reuse in other algorithms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this will come pretty handy when I start applying this parallel modality to other algorithms. I'm writing a refactor plan to st_distance and this will be factored out as part of that.