diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c597e1695..62d87597c 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -103,6 +103,7 @@ rapids_cpm_init() # find or add cuDF include(cmake/thirdparty/CUSPATIAL_GetCUDF.cmake) + ################################################################################################### # - library targets ------------------------------------------------------------------------------- diff --git a/cpp/include/cuspatial/experimental/coordinate_transform.cuh b/cpp/include/cuspatial/experimental/coordinate_transform.cuh index a4b2e074a..fd64d8157 100644 --- a/cpp/include/cuspatial/experimental/coordinate_transform.cuh +++ b/cpp/include/cuspatial/experimental/coordinate_transform.cuh @@ -43,6 +43,10 @@ namespace cuspatial { * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible and mutable. * @tparam T the floating-point coordinate value type of input longitude/latitude coordinates. * + * @pre `lonlat_first` may equal `xy_first`, but the range `[lonlat_first, lonlat_last)` + * shall not otherwise overlap the range `[xy_first, xy_first + std::distance(lonlat_first, + * lonlat_last))`. + * * @return Output iterator to the element past the last x/y coordinate computed. * * [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator diff --git a/cpp/include/cuspatial/experimental/detail/hausdorff.cuh b/cpp/include/cuspatial/experimental/detail/hausdorff.cuh new file mode 100644 index 000000000..41d127df9 --- /dev/null +++ b/cpp/include/cuspatial/experimental/detail/hausdorff.cuh @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2022, 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 +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +namespace cuspatial { + +namespace detail { + +template +constexpr auto magnitude_squared(T a, T b) +{ + return a * a + b * b; +} + +/** + * @internal + * @brief computes Hausdorff distance by equally dividing up work on a per-thread basis. + * + * Each thread is responsible for computing the distance from a single point in the input against + * all other points in the input. Because points in the input can originate from different spaces, + * each thread must know which spaces it is comparing. For the LHS argument, the point is always + * the same for any given thread and is determined once for that thread using a binary search of + * the provided space_offsets. Therefore if space 0 contains 10 points, the first 10 threads will + * know that the LHS space is 0. The 11th thread will know the LHS space is 1, and so on depending + * on the sizes/offsets of each space. Each thread then loops over each space, and uses an inner + * loop to loop over each point within that space, thereby knowing the RHS space and RHS point. + * the thread computes the minimum distance from it's LHS point to _any_ point in the RHS space, as + * this is the first step to computing Hausdorff distance. The second step of computing Hausdorff + * distance is to determine the maximum of these minimums, which is done by each thread writing + * it's minimum to the output using atomicMax. This is done once per thread per RHS space. Once + * all threads have run to completion, all "maximums of the minumum distances" (aka, directed + * Hausdorff distances) reside in the output. + * + * @tparam T type of coordinate, either float or double. + * @tparam Index type of indices, e.g. int32_t. + * @tparam PointsIter Iterator to input points. Points must be of a type that is convertible to + * `cuspatial::vec_2d`. Must meet the requirements of [LegacyRandomAccessIterator][LinkLRAI] and + * be device-accessible. + * @tparam OffsetsIter Iterator to space offsets. Value type must be integral. Must meet the + * requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam OutputIt Output iterator. Must meet the requirements of + * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible and mutable. + * + * @param num_points number of total points in the input (sum of points from all spaces) + * @param points x/y points to compute the distances between + * @param num_spaces number of spaces in the input + * @param space_offsets starting position of first point in each space + * @param results directed Hausdorff distances computed by kernel + */ +template +__global__ void kernel_hausdorff( + Index num_points, PointIt points, Index num_spaces, OffsetIt space_offsets, OutputIt results) +{ + using Point = typename std::iterator_traits::value_type; + + // determine the LHS point this thread is responsible for. + auto const thread_idx = blockIdx.x * blockDim.x + threadIdx.x; + Index const lhs_p_idx = thread_idx; + + if (lhs_p_idx >= num_points) { return; } + + auto const lhs_space_iter = + thrust::upper_bound(thrust::seq, space_offsets, space_offsets + num_spaces, lhs_p_idx); + // determine the LHS space this point belongs to. + Index const lhs_space_idx = thrust::distance(space_offsets, thrust::prev(lhs_space_iter)); + + // get the coordinates of this LHS point. + Point const lhs_p = points[lhs_p_idx]; + + // loop over each RHS space, as determined by spa ce_offsets + for (uint32_t rhs_space_idx = 0; rhs_space_idx < num_spaces; rhs_space_idx++) { + // determine the begin/end offsets of points contained within this RHS space. + Index const rhs_p_idx_begin = space_offsets[rhs_space_idx]; + Index const rhs_p_idx_end = + (rhs_space_idx + 1 == num_spaces) ? num_points : space_offsets[rhs_space_idx + 1]; + + // each space must contain at least one point, this initial value is just an identity value to + // simplify calculations. If a space contains <= 0 points, then this initial value will be + // written to the output, which can serve as a signal that the input is ill-formed. + auto min_distance_squared = std::numeric_limits::max(); + + // loop over each point in the current RHS space + for (uint32_t rhs_p_idx = rhs_p_idx_begin; rhs_p_idx < rhs_p_idx_end; rhs_p_idx++) { + // get the x and y coordinate of this RHS point + Point const rhs_p = thrust::raw_reference_cast(points[rhs_p_idx]); + + // get distance between the LHS and RHS point + auto const distance_squared = magnitude_squared(rhs_p.x - lhs_p.x, rhs_p.y - lhs_p.y); + + // remember only smallest distance from this LHS point to any RHS point. + min_distance_squared = min(min_distance_squared, distance_squared); + } + + // determine the output offset for this pair of spaces (LHS, RHS) + Index output_idx = lhs_space_idx * num_spaces + rhs_space_idx; + + // use atomicMax to find the maximum of the minimum distance calculated for each space pair. + atomicMax(&thrust::raw_reference_cast(*(results + output_idx)), + static_cast(std::sqrt(min_distance_squared))); + } +} + +} // namespace detail + +template +OutputIt directed_hausdorff_distance(PointIt points_first, + PointIt points_last, + OffsetIt space_offsets_first, + OffsetIt space_offsets_last, + OutputIt distance_first, + rmm::cuda_stream_view stream) +{ + using Point = typename std::iterator_traits::value_type; + using Index = typename std::iterator_traits::value_type; + using T = typename Point::value_type; + using OutputT = typename std::iterator_traits::value_type; + + static_assert(std::is_convertible_v>, + "Input points must be convertible to cuspatial::vec_2d"); + static_assert(detail::is_floating_point(), + "Hausdorff supports only floating-point coordinates."); + static_assert(std::is_integral_v, "Indices must be integral"); + + auto const num_points = std::distance(points_first, points_last); + auto const num_spaces = std::distance(space_offsets_first, space_offsets_last); + + CUSPATIAL_EXPECTS(num_points >= num_spaces, "At least one point is required for each space"); + CUSPATIAL_EXPECTS(num_spaces < (1 << 15), "Total number of spaces must be less than 2^16"); + + auto const num_results = num_spaces * num_spaces; + + if (num_results > 0) { + // Due to hausdorff kernel using `atomicMax` for output, the output must be initialized to <= 0 + // here the output is being initialized to -1, which should always be overwritten. If -1 is + // found in the output, there is a bug where the output is not being written to in the hausdorff + // kernel. + thrust::fill_n(rmm::exec_policy(stream), distance_first, num_results, -1); + + auto const threads_per_block = 64; + auto const num_tiles = (num_points + threads_per_block - 1) / threads_per_block; + + detail::kernel_hausdorff + <<>>( + num_points, points_first, num_spaces, space_offsets_first, distance_first); + + CUSPATIAL_CUDA_TRY(cudaGetLastError()); + } + + return distance_first + num_results; +} + +} // namespace cuspatial diff --git a/cpp/include/cuspatial/experimental/hausdorff.cuh b/cpp/include/cuspatial/experimental/hausdorff.cuh new file mode 100644 index 000000000..a7a5b176b --- /dev/null +++ b/cpp/include/cuspatial/experimental/hausdorff.cuh @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022, 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 +#include + +#include + +namespace cuspatial { + +/** + * @ingroup distance + * @brief Computes Hausdorff distances for all pairs in a collection of spaces + * + * https://en.wikipedia.org/wiki/Hausdorff_distance + * + * Example in 1D (this function operates in 2D): + * ``` + * spaces + * [0 2 5] [9] [3 7] + * + * spaces represented as points per space and concatenation of all points + * [0 2 5 9 3 7] [3 1 2] + * + * note: the following matrices are visually separated to highlight the relationship of a pair of + * points with the pair of spaces from which it is produced + * + * cartesian product of all + * points by pair of spaces distance between points + * +----------+----+-------+ +---------+---+------+ + * : 00 02 05 : 09 : 03 07 : : 0 2 5 : 9 : 3 7 : + * : 20 22 25 : 29 : 23 27 : : 2 0 3 : 7 : 1 5 : + * : 50 52 55 : 59 : 53 57 : : 5 3 0 : 4 : 2 2 : + * +----------+----+-------+ +---------+---+------+ + * : 90 92 95 : 99 : 93 97 : : 9 7 4 : 0 : 6 2 : + * +----------+----+-------+ +---------+---+------+ + * : 30 32 35 : 39 : 33 37 : : 3 1 2 : 6 : 0 4 : + * : 70 72 75 : 79 : 73 77 : : 7 5 2 : 2 : 4 0 : + * +----------+----+-------+ +---------+---+------+ + + * minimum distance from + * every point in one Hausdorff distance is + * space to any point in the maximum of the + * the other space minimum distances + * +----------+----+-------+ +---------+---+------+ + * : 0 : 9 : 3 : : 0 : 9 : 3 : + * : 0 : 7 : 1 : : : : : + * : 0 : 4 : 2 : : : : : + * +----------+----+-------+ +---------+---+------+ + * : 4 : 0 : 2 : : 4 : 0 : 2 : + * +----------+----+-------+ +---------+---+------+ + * : 1 : 6 : 0 : : : 6 : 0 : + * : 2 : 2 : 0 : : 2 : : : + * +----------+----+-------+ +---------+---+------+ + * + * returned as concatenation of columns + * [0 2 4 3 0 2 9 6 0] + * ``` + * + * @param[in] points_first: xs: beginning of range of (x,y) points + * @param[in] points_lasts: xs: end of range of (x,y) points + * @param[in] space_offsets_first: beginning of range of indices to each space. + * @param[in] space_offsets_first: end of range of indices to each space. Last index is the last + * @param[in] distance_first: beginning of range of output Hausdorff distance for each pair of + * spaces + * + * @tparam PointIt Iterator to input points. Points must be of a type that is convertible to + * `cuspatial::vec_2d`. Must meet the requirements of [LegacyRandomAccessIterator][LinkLRAI] and + * be device-accessible. + * @tparam OffsetIt Iterator to space offsets. Value type must be integral. Must meet the + * requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam OutputIt Output iterator. Must meet the requirements of + * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible and mutable. + * + * @pre All iterators must have the same underlying floating-point value type. + * + * @return Output iterator to the element past the last distance computed. + * + * @note Hausdorff distances are asymmetrical + */ +template +OutputIt directed_hausdorff_distance(PointIt points_first, + PointIt points_last, + OffsetIt space_offsets_first, + OffsetIt space_offsets_last, + OutputIt distance_first, + rmm::cuda_stream_view stream = rmm::cuda_stream_default); + +} // namespace cuspatial + +#include diff --git a/cpp/include/cuspatial/experimental/haversine.cuh b/cpp/include/cuspatial/experimental/haversine.cuh index 47d308a04..4b743f690 100644 --- a/cpp/include/cuspatial/experimental/haversine.cuh +++ b/cpp/include/cuspatial/experimental/haversine.cuh @@ -56,12 +56,6 @@ namespace cuspatial { * `cuspatial::lonlat_2d`. * @tparam T The underlying coordinate type. Must be a floating-point type. * - * @pre `a_lonlat_first` may equal `distance_first`, but the range `[a_lonlat_first, a_lonlat_last)` - * shall not overlap the range `[distance_first, distance_first + (a_lonlat_last - a_lonlat_last)) - * otherwise. - * @pre `b_lonlat_first` may equal `distance_first`, but the range `[b_lonlat_first, b_lonlat_last)` - * shall not overlap the range `[distance_first, distance_first + (b_lonlat_last - b_lonlat_last)) - * otherwise. * @pre All iterators must have the same `Location` type, with the same underlying floating-point * coordinate type (e.g. `cuspatial::lonlat_2d`). * diff --git a/cpp/include/cuspatial/experimental/type_utils.hpp b/cpp/include/cuspatial/experimental/type_utils.hpp index 502b9218a..9ae6f4922 100644 --- a/cpp/include/cuspatial/experimental/type_utils.hpp +++ b/cpp/include/cuspatial/experimental/type_utils.hpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include #include #include @@ -105,6 +107,13 @@ auto make_vec_2d_iterator(FirstIter first, SecondIter second) * [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator * "LegacyRandomAccessIterator" */ +template +auto make_vec_2d_iterator(FirstIter first, SecondIter second) +{ + using T = typename std::iterator_traits::value_type; + return make_vec_2d_iterator>(first, second); +} + template auto make_lonlat_iterator(FirstIter first, SecondIter second) { diff --git a/cpp/include/doxygen_groups.h b/cpp/include/doxygen_groups.h index fa14f4aec..5f6c96bf2 100644 --- a/cpp/include/doxygen_groups.h +++ b/cpp/include/doxygen_groups.h @@ -49,6 +49,7 @@ * * @file linestring_distance.hpp * @file hausdorff.hpp + * @file experimental/hausdorff.cuh * @file haversine.hpp * @file haversine.cuh * @} diff --git a/cpp/src/spatial/hausdorff.cu b/cpp/src/spatial/hausdorff.cu index 83106bb70..599745e4a 100644 --- a/cpp/src/spatial/hausdorff.cu +++ b/cpp/src/spatial/hausdorff.cu @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021, NVIDIA CORPORATION. + * Copyright (c) 2019-2022, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ #include +#include +#include #include #include @@ -37,99 +39,6 @@ namespace { -using size_type = cudf::size_type; - -template -constexpr auto magnitude_squared(T a, T b) -{ - return a * a + b * b; -} - -/** - * @brief computes Hausdorff distance by equally dividing up work on a per-thread basis. - * - * Each thread is responsible for computing the distance from a single point in the input against - * all other points in the input. Because points in the input can originate from different spaces, - * each thread must know which spaces it is comparing. For the LHS argument, the point is always - * the same for any given thread and is determined once for that thread using a binary search of - * the provided space_offsets. Therefore if space 0 contains 10 points, the first 10 threads will - * know that the LHS space is 0. The 11th thread will know the LHS space is 1, and so on depending - * on the sizes/offsets of each space. Each thread then loops over each space, and uses an inner - * loop to loop over each point within that space, thereby knowing the RHS space and RHS point. - * the thread computes the minimum distance from it's LHS point to _any_ point in the RHS space, as - * this is the first step to computing Hausdorff distance. The second step of computing Hausdorff - * distance is to determine the maximum of these minimums, which is done by each thread writing - * it's minimum to the output using atomicMax. This is done once per thread per RHS space. Once - * all threads have run to completion, all "maximums of the minumum distances" (aka, directed - * Hausdorff distances) reside in the output. - * - * @tparam T type of coordinate, either float or double. - * @param num_points number of total points in the input (sum of points from all spaces) - * @param xs x coordinates - * @param ys y coordinates - * @param num_spaces number of spaces in the input - * @param space_offsets starting position of each first point in each space - * @param results directed Hausdorff distances computed by kernel - * @return - */ -template -__global__ void kernel_hausdorff(size_type num_points, - T const* xs, - T const* ys, - size_type num_spaces, - size_type const* space_offsets, - T* results) -{ - // determine the LHS point this thread is responsible for. - auto const thread_idx = blockIdx.x * blockDim.x + threadIdx.x; - auto const lhs_p_idx = thread_idx; - - if (lhs_p_idx >= num_points) { return; } - - // determine the LHS space this point belongs to. - auto const lhs_space_idx = - thrust::distance( - space_offsets, - thrust::upper_bound(thrust::seq, space_offsets, space_offsets + num_spaces, lhs_p_idx)) - - 1; - - // get the x and y coordinate of this LHS point. - auto const lhs_p_x = xs[lhs_p_idx]; - auto const lhs_p_y = ys[lhs_p_idx]; - - // loop over each RHS space, as determined by space_offsets - for (uint32_t rhs_space_idx = 0; rhs_space_idx < num_spaces; rhs_space_idx++) { - // determine the begin/end offsets of points contained within this RHS space. - auto const rhs_p_idx_begin = space_offsets[rhs_space_idx]; - auto const rhs_p_idx_end = - (rhs_space_idx + 1 == num_spaces) ? num_points : space_offsets[rhs_space_idx + 1]; - - // each space must contain at least one point, this initial value is just an identity value to - // simplify calculations. If a space contains <= 0 points, then this initial value will be - // written to the output, which can serve as a signal that their input is ill-formed. - auto min_distance_squared = std::numeric_limits::max(); - - // loop over each point in the current RHS space - for (uint32_t rhs_p_idx = rhs_p_idx_begin; rhs_p_idx < rhs_p_idx_end; rhs_p_idx++) { - // get the x and y coordinate of this RHS point - auto const rhs_p_x = xs[rhs_p_idx]; - auto const rhs_p_y = ys[rhs_p_idx]; - - // get distance between the LHS and RHS point - auto const distance_squared = magnitude_squared(rhs_p_x - lhs_p_x, rhs_p_y - lhs_p_y); - - // remember only smallest distance from this LHS point to any RHS point. - min_distance_squared = min(min_distance_squared, distance_squared); - } - - // determine the output offset for this pair of spaces (LHS, RHS) - auto output_idx = lhs_space_idx * num_spaces + rhs_space_idx; - - // use atomicMax to find the maximum of the minimum distance calculated for each space pair. - atomicMax(results + output_idx, sqrt(min_distance_squared)); - } -} - struct hausdorff_functor { template std::enable_if_t::value, std::unique_ptr> operator()( @@ -161,26 +70,15 @@ struct hausdorff_functor { auto const result_view = result->mutable_view(); - // due to hausdorff kernel using `atomicMax` for output, the output must be initialized to <= 0 - // here the output is being initialized to -1, which should always be overwritten. If -1 is - // found in the output, there is a bug where the output is not being written to in the hausdorff - // kernel. - thrust::fill(rmm::exec_policy(stream), result_view.begin(), result_view.end(), -1); - - auto const threads_per_block = 64; - auto const num_tiles = (num_points + threads_per_block - 1) / threads_per_block; + auto points_iter = cuspatial::make_vec_2d_iterator(xs.begin(), ys.begin()); + auto space_offsets_iter = space_offsets.begin(); - auto kernel = kernel_hausdorff; - - kernel<<>>( - num_points, - xs.data(), - ys.data(), - num_spaces, - space_offsets.begin(), - result_view.begin()); - - CUSPATIAL_CUDA_TRY(cudaGetLastError()); + cuspatial::directed_hausdorff_distance(points_iter, + points_iter + num_points, + space_offsets_iter, + space_offsets_iter + num_spaces, + result_view.begin(), + stream); return result; } @@ -201,9 +99,6 @@ std::unique_ptr directed_hausdorff_distance(cudf::column_view cons CUSPATIAL_EXPECTS(not xs.has_nulls() and not ys.has_nulls() and not space_offsets.has_nulls(), "Inputs must not have nulls."); - CUSPATIAL_EXPECTS(xs.size() >= space_offsets.size(), - "At least one point is required for each space"); - return cudf::type_dispatcher( xs.type(), hausdorff_functor(), xs, ys, space_offsets, rmm::cuda_stream_default, mr); } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index a2c400788..b7a44768f 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -105,6 +105,10 @@ ConfigureTest(SPATIAL_WINDOW_POINT_TEST ConfigureTest(HAVERSINE_TEST_EXP experimental/spatial/haversine_test.cu) +ConfigureTest(HAUSDORFF_TEST_EXP + experimental/spatial/hausdorff_test.cu) + + ConfigureTest(LINESTRING_DISTANCE_TEST_EXP experimental/spatial/linestring_distance_test.cu) diff --git a/cpp/tests/experimental/spatial/hausdorff_test.cu b/cpp/tests/experimental/spatial/hausdorff_test.cu new file mode 100644 index 000000000..3429fb2c9 --- /dev/null +++ b/cpp/tests/experimental/spatial/hausdorff_test.cu @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2022, 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 +#include +#include + +#include + +#include +#include + +#include +#include + +template +struct HausdorffTest : public ::testing::Test { + template + void test(std::vector const& points, + std::vector const& space_offsets, + std::vector const& expected) + { + auto const d_points = rmm::device_vector{points}; + auto const d_space_offsets = rmm::device_vector{space_offsets}; + + auto const num_distances = space_offsets.size() * space_offsets.size(); + auto distances = rmm::device_vector{num_distances}; + + auto const distances_end = cuspatial::directed_hausdorff_distance(d_points.begin(), + d_points.end(), + d_space_offsets.begin(), + d_space_offsets.end(), + distances.begin()); + + thrust::host_vector h_distances(distances); + + EXPECT_THAT(distances, ::testing::Pointwise(::testing::DoubleEq(), expected)); + EXPECT_EQ(num_distances, std::distance(distances.begin(), distances_end)); + } +}; + +using TestTypes = ::testing::Types; +TYPED_TEST_CASE(HausdorffTest, TestTypes); + +TYPED_TEST(HausdorffTest, Empty) +{ + this->template test, uint32_t>({}, {}, {}); +} + +TYPED_TEST(HausdorffTest, Simple) +{ + this->template test, uint32_t>( + {{0, 0}, {1, 0}, {0, 1}, {0, 2}}, + {{0, 2}}, + {{0.0, static_cast(std::sqrt(2.0)), 2.0, 0.0}}); +} + +TYPED_TEST(HausdorffTest, SingleTrajectorySinglePoint) +{ + this->template test, uint32_t>({{152.2, 2351.0}}, {{0}}, {{0.0}}); +} + +TYPED_TEST(HausdorffTest, TwoShortSpaces) +{ + this->template test, uint32_t>( + {{0, 0}, {5, 12}, {4, 3}}, {{0, 1}}, {{0.0, 5.0, 13.0, 0.0}}); +} + +TYPED_TEST(HausdorffTest, TwoShortSpaces2) +{ + this->template test, uint32_t>( + {{1, 1}, {5, 12}, {4, 3}, {2, 8}, {3, 4}, {7, 7}}, + {{0, 3, 4}}, + {{0.0, + 7.0710678118654755, + 5.3851648071345037, + 5.0000000000000000, + 0.0, + 4.1231056256176606, + 5.0, + 5.0990195135927854, + 0.0}}); +} + +TYPED_TEST(HausdorffTest, ThreeSpacesLengths543) +{ + this->template test, uint32_t>({{0.0, 1.0}, + {1.0, 2.0}, + {2.0, 3.0}, + {3.0, 5.0}, + {1.0, 7.0}, + {3.0, 0.0}, + {5.0, 2.0}, + {6.0, 3.0}, + {5.0, 6.0}, + {4.0, 1.0}, + {7.0, 3.0}, + {4.0, 6.0}}, + {{0, 5, 9}}, + {{0.0000000000000000, + 4.1231056256176606, + 4.0000000000000000, + 3.6055512754639896, + 0.0000000000000000, + 1.4142135623730951, + 4.4721359549995796, + 1.4142135623730951, + 0.0000000000000000}}); +} + +TYPED_TEST(HausdorffTest, MoreSpacesThanPoints) +{ + EXPECT_THROW( + (this->template test, uint32_t>({{0, 0}}, {{0, 1}}, {{0.0}})), + cuspatial::logic_error); +} + +template +void generic_hausdorff_test() +{ + constexpr uint64_t num_points = + static_cast(elements_per_space) * static_cast(num_spaces); + constexpr auto num_distances = num_spaces * num_spaces; + + using vec_2d = cuspatial::vec_2d; + + auto zero_iter = thrust::make_constant_iterator({0, 0}); + auto counting_iter = thrust::make_counting_iterator(0); + auto space_offset_iter = thrust::make_transform_iterator( + counting_iter, [] __device__(auto idx) { return idx * elements_per_space; }); + + auto distances = rmm::device_vector{num_distances}; + auto expected = rmm::device_vector{num_distances, 0}; + + auto distances_end = cuspatial::directed_hausdorff_distance(zero_iter, + zero_iter + num_points, + space_offset_iter, + space_offset_iter + num_spaces, + distances.begin()); + + EXPECT_EQ(distances, expected); + EXPECT_EQ(num_distances, std::distance(distances.begin(), distances_end)); +} + +TYPED_TEST(HausdorffTest, 500Spaces100Points) { generic_hausdorff_test(); } + +TYPED_TEST(HausdorffTest, 10000Spaces10Points) { generic_hausdorff_test(); } + +TYPED_TEST(HausdorffTest, 10Spaces10000Points) { generic_hausdorff_test(); } diff --git a/cpp/tests/spatial/hausdorff_test.cpp b/cpp/tests/spatial/hausdorff_test.cpp index ca8f5a6a3..665619285 100644 --- a/cpp/tests/spatial/hausdorff_test.cpp +++ b/cpp/tests/spatial/hausdorff_test.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021, NVIDIA CORPORATION. + * Copyright (c) 2020-2022, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,30 +35,6 @@ using namespace test; constexpr cudf::test::debug_output_level verbosity{cudf::test::debug_output_level::ALL_ERRORS}; -template -void generic_hausdorff_test(rmm::mr::device_memory_resource* mr) -{ - constexpr uint64_t num_elements = - static_cast(elements_per_space) * static_cast(num_spaces); - - auto zero_iter = thrust::make_constant_iterator(0); - auto counting_iter = thrust::make_counting_iterator(0); - auto space_offset_iter = thrust::make_transform_iterator( - counting_iter, [](auto idx) { return idx * elements_per_space; }); - - auto x = cudf::test::fixed_width_column_wrapper(zero_iter, zero_iter + num_elements); - auto y = cudf::test::fixed_width_column_wrapper(zero_iter, zero_iter + num_elements); - auto space_offsets = cudf::test::fixed_width_column_wrapper( - space_offset_iter, space_offset_iter + num_spaces); - - auto expected = - cudf::test::fixed_width_column_wrapper(zero_iter, zero_iter + (num_spaces * num_spaces)); - - auto actual = std::move(cuspatial::directed_hausdorff_distance(x, y, space_offsets, mr)); - - expect_columns_equivalent(expected, actual->view()); -} - template struct HausdorffTest : public BaseFixture { }; @@ -80,89 +56,6 @@ TYPED_TEST(HausdorffTest, Empty) expect_columns_equivalent(expected, actual->view(), verbosity); } -TYPED_TEST(HausdorffTest, Simple) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper({0, 1, 0, 0}); - auto y = cudf::test::fixed_width_column_wrapper({0, 0, 1, 2}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0, 2}); - - auto expected = cudf::test::fixed_width_column_wrapper({0.0, std::sqrt(2.0), 2.0, 0.0}); - - auto actual = cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()); - - expect_columns_equivalent(expected, actual->view(), verbosity); -} - -TYPED_TEST(HausdorffTest, SingleTrajectorySinglePoint) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper({152.2}); - auto y = cudf::test::fixed_width_column_wrapper({2351.0}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0}); - - auto expected = cudf::test::fixed_width_column_wrapper({0}); - - auto actual = cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()); - - expect_columns_equivalent(expected, actual->view(), verbosity); -} - -TYPED_TEST(HausdorffTest, TwoShortSpaces) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper({0, 5, 4}); - auto y = cudf::test::fixed_width_column_wrapper({0, 12, 3}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0, 1}); - - auto expected = cudf::test::fixed_width_column_wrapper({0, 5, 13, 0}); - - auto actual = cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()); - - expect_columns_equivalent(expected, actual->view(), verbosity); -} - -TYPED_TEST(HausdorffTest, TwoShortSpaces2) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper({1, 5, 4, 2, 3, 7}); - auto y = cudf::test::fixed_width_column_wrapper({1, 12, 3, 8, 4, 7}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0, 3, 4}); - - auto expected = cudf::test::fixed_width_column_wrapper({0.0, - 7.0710678118654755, - 5.3851648071345037, - 5.0000000000000000, - 0.0, - 4.1231056256176606, - 5.0, - 5.0990195135927854, - 0.0}); - - auto actual = cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()); - - expect_columns_equivalent(expected, actual->view(), verbosity); -} - -TYPED_TEST(HausdorffTest, 500Spaces100Points) -{ - generic_hausdorff_test(this->mr()); -} - -TYPED_TEST(HausdorffTest, 10000Spaces10Points) -{ - generic_hausdorff_test(this->mr()); -} - -TYPED_TEST(HausdorffTest, 10Spaces10000Points) -{ - generic_hausdorff_test(this->mr()); -} - TYPED_TEST(HausdorffTest, MoreSpacesThanPoints) { using T = TypeParam; @@ -174,42 +67,3 @@ TYPED_TEST(HausdorffTest, MoreSpacesThanPoints) EXPECT_THROW(cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()), cuspatial::logic_error); } - -TYPED_TEST(HausdorffTest, TooFewPoints) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper({0}); - auto y = cudf::test::fixed_width_column_wrapper({0}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0, 1}); - - EXPECT_THROW(cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()), - cuspatial::logic_error); -} - -TYPED_TEST(HausdorffTest, ThreeSpacesLengths543) -{ - using T = TypeParam; - - auto x = cudf::test::fixed_width_column_wrapper( - {0.0, 1.0, 2.0, 3.0, 1.0, 3.0, 5.0, 6.0, 5.0, 4.0, 7.0, 4.0}); - auto y = cudf::test::fixed_width_column_wrapper( - {1.0, 2.0, 3.0, 5.0, 7.0, 0.0, 2.0, 3.0, 6.0, 1.0, 3.0, 6.0}); - auto space_offsets = cudf::test::fixed_width_column_wrapper({0, 5, 9}); - - auto expected = cudf::test::fixed_width_column_wrapper({ - 0.0000000000000000, - 4.1231056256176606, - 4.0000000000000000, - 3.6055512754639896, - 0.0000000000000000, - 1.4142135623730951, - 4.4721359549995796, - 1.4142135623730951, - 0.0000000000000000, - }); - - auto actual = cuspatial::directed_hausdorff_distance(x, y, space_offsets, this->mr()); - - expect_columns_equivalent(expected, actual->view(), verbosity); -}