Skip to content
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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7022c1d
initial
isVoid Mar 24, 2023
5686be3
add segment iterator and tests
isVoid Mar 28, 2023
a74af14
WIP
isVoid Mar 28, 2023
1c28faa
making progress towards getting the right index
isVoid Mar 29, 2023
243d2d9
commiting progress
isVoid Mar 29, 2023
0da0afc
able to get the right segment combinations from within the kernel, ne…
isVoid Mar 31, 2023
49e43aa
[skip-ci] initial port from feature/linestring_polygon_distance_header
isVoid Mar 31, 2023
e8e45d0
Apply suggestions from code review
isVoid Mar 31, 2023
fadf5c8
Update cpp/include/cuspatial/experimental/detail/functors.cuh
isVoid Mar 31, 2023
fe31542
address review comments
isVoid Mar 31, 2023
af0c35e
address review comments
isVoid Mar 31, 2023
05bf7ff
[skip-ci] address review coments
isVoid Mar 31, 2023
2435126
Add docstrings for functors
isVoid Mar 31, 2023
98a4a0f
[skip-ci] adds linestring_count test
isVoid Apr 1, 2023
9b72723
[skip-ci] revert removed code
isVoid Apr 1, 2023
6971200
style
isVoid Apr 1, 2023
6f7a226
update unexpected changes
isVoid Apr 1, 2023
641934f
license
isVoid Apr 1, 2023
c580876
Merge branch 'feature/segment_iterators' into feature/linestring_poly…
isVoid Apr 1, 2023
890e95b
Update cpp/include/cuspatial/experimental/detail/ranges/multilinestri…
isVoid Apr 1, 2023
040e432
making a bit of progress
isVoid Apr 2, 2023
7c3603c
fix broken multilinestring->multipoint constructor
isVoid Apr 2, 2023
5966ad0
Merge branch 'feature/segment_iterators' of github.com:isVoid/cuspati…
isVoid Apr 2, 2023
70ca1b6
fix broken multilinestring->multipoint constructor
isVoid Apr 2, 2023
9205533
passing existing tests
isVoid Apr 3, 2023
4bb3ca3
fix wrong test setup and update docs
isVoid Apr 3, 2023
9f34700
Merge branch 'feature/segment_iterators' into feature/linestring_poly…
isVoid Apr 3, 2023
8f20feb
style
isVoid Apr 3, 2023
3ffe5ee
Delete point_in_multipolygon.cuh
isVoid Apr 3, 2023
894f870
Delete linestring_polygon_distance.hpp
isVoid Apr 3, 2023
2b99555
cleanup - first round
isVoid Apr 3, 2023
656d093
Merge branch 'feature/linestring_polygon_distance_header' of github.c…
isVoid Apr 3, 2023
62d821f
merge duplicate code
isVoid Apr 3, 2023
7d22749
add missing header
isVoid Apr 3, 2023
9051944
style
isVoid Apr 3, 2023
e082aa1
fix OB access error
isVoid Apr 4, 2023
d94e060
address reviews
isVoid Apr 4, 2023
5dedc52
address reviews
isVoid Apr 4, 2023
314ad04
address review comments
isVoid Apr 5, 2023
26f272c
Merge branch 'branch-23.04' of https://github.com/rapidsai/cuspatial …
isVoid Apr 5, 2023
75a8fd2
style
isVoid Apr 5, 2023
dd1b09c
fix indirect dependency on thrust includes
isVoid Apr 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions cpp/include/cuspatial/experimental/detail/distance_utils.cuh
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
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()));
Comment on lines +128 to +144
Copy link
Contributor

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.

Copy link
Contributor Author

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.


// 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#pragma once

#include "distance_utils.cuh"

#include <cuspatial/cuda_utils.hpp>
#include <cuspatial/detail/iterator.hpp>
#include <cuspatial/detail/utility/device_atomics.cuh>
Expand Down Expand Up @@ -45,38 +47,6 @@
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 Kernel to compute the distance between pairs of point and polygon.
*/
Expand Down Expand Up @@ -128,36 +98,7 @@ OutputIt pairwise_point_polygon_distance(MultiPointRange multipoints,

if (multipoints.size() == 0) return distances_first;

// 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.
auto multipoint_intersects = [&]() {
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;
}();
auto multipoint_intersects = point_polygon_intersects(multipoints, multipolygons, stream);

thrust::fill(rmm::exec_policy(stream),
distances_first,
Expand Down
Loading