Loading...
Searching...
No Matches
vector_equality.hpp
1/*
2 * Copyright (c) 2022-2024, NVIDIA CORPORATION.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <cuspatial_test/test_util.cuh>
20
23#include <cuspatial/traits.hpp>
24
25#include <rmm/device_uvector.hpp>
26#include <rmm/device_vector.hpp>
27
28#include <thrust/host_vector.h>
29
30#include <gmock/gmock.h>
31#include <gtest/gtest.h>
32
33#include <limits>
34#include <type_traits>
35
36namespace cuspatial {
37namespace test {
38
45template <typename T>
46auto floating_eq_by_ulp(T val)
47{
48 if constexpr (std::is_same_v<T, float>) {
49 return ::testing::NanSensitiveFloatEq(val);
50 } else {
51 return ::testing::NanSensitiveDoubleEq(val);
52 }
53}
54
58template <typename T>
59auto floating_eq_by_abs_error(T val, T abs_error)
60{
61 if constexpr (std::is_same_v<T, float>) {
62 return ::testing::NanSensitiveFloatNear(val, abs_error);
63 } else {
64 return ::testing::NanSensitiveDoubleNear(val, abs_error);
65 }
66}
67
68MATCHER(vec_2d_matcher,
69 std::string(negation ? "are not" : "are") + " approximately equal vec_2d structs")
70{
71 auto lhs = std::get<0>(arg);
72 auto rhs = std::get<1>(arg);
73
74 if (::testing::Matches(floating_eq_by_ulp(rhs.x))(lhs.x) &&
75 ::testing::Matches(floating_eq_by_ulp(rhs.y))(lhs.y))
76 return true;
77
78 *result_listener << std::fixed
79 << std::setprecision(std::numeric_limits<decltype(lhs)>::max_digits10) << lhs
80 << " != " << rhs;
81
82 return false;
83}
84
85MATCHER_P(vec_2d_near_matcher,
86 abs_error,
87 std::string(negation ? "are not" : "are") + " approximately equal vec_2d structs")
88{
89 auto lhs = std::get<0>(arg);
90 auto rhs = std::get<1>(arg);
91
92 if (::testing::Matches(floating_eq_by_abs_error(rhs.x, abs_error))(lhs.x) &&
93 ::testing::Matches(floating_eq_by_abs_error(rhs.y, abs_error))(lhs.y))
94 return true;
95
96 *result_listener << std::fixed
97 << std::setprecision(std::numeric_limits<decltype(lhs)>::max_digits10) << lhs
98 << " != " << rhs;
99
100 return false;
101}
102
103MATCHER(float_matcher, std::string(negation ? "are not" : "are") + " approximately equal floats")
104{
105 auto lhs = std::get<0>(arg);
106 auto rhs = std::get<1>(arg);
107
108 if (::testing::Matches(floating_eq_by_ulp(rhs))(lhs)) return true;
109
110 *result_listener << std::fixed
111 << std::setprecision(std::numeric_limits<decltype(lhs)>::max_digits10) << lhs
112 << " != " << rhs;
113
114 return false;
115}
116
117MATCHER_P(float_near_matcher,
118 abs_error,
119 std::string(negation ? "are not" : "are") + " approximately equal floats")
120{
121 auto lhs = std::get<0>(arg);
122 auto rhs = std::get<1>(arg);
123
124 if (::testing::Matches(floating_eq_by_abs_error(rhs, abs_error))(lhs)) return true;
125
126 *result_listener << std::fixed
127 << std::setprecision(std::numeric_limits<decltype(lhs)>::max_digits10) << lhs
128 << " != " << rhs;
129
130 return false;
131}
132
133MATCHER_P(optional_matcher, m, std::string(negation ? "are not" : "are") + " equal optionals")
134{
135 auto lhs = std::get<0>(arg);
136 auto rhs = std::get<1>(arg);
137
138 if (lhs.has_value() != rhs.has_value()) {
139 *result_listener << "lhs " << (lhs.has_value() ? "" : "does not ") << "has value, while rhs "
140 << (rhs.has_value() ? "" : "does not ") << "has value.";
141 return false;
142 } else if (!lhs.has_value() && !rhs.has_value()) {
143 return true;
144 } else
145 return ExplainMatchResult(m, std::tuple(lhs.value(), rhs.value()), result_listener);
146}
147
148template <typename Vector1, typename Vector2>
149inline void expect_vector_equivalent(Vector1 const& lhs, Vector2 const& rhs)
150{
151 using T = typename Vector1::value_type;
152 static_assert(std::is_same_v<T, typename Vector2::value_type>, "Value type mismatch.");
153
154 if constexpr (cuspatial::is_vec_2d<T>) {
155 EXPECT_THAT(to_host<T>(lhs), ::testing::Pointwise(vec_2d_matcher(), to_host<T>(rhs)));
156 } else if constexpr (std::is_floating_point_v<T>) {
157 EXPECT_THAT(to_host<T>(lhs), ::testing::Pointwise(float_matcher(), to_host<T>(rhs)));
158 } else if constexpr (std::is_integral_v<T>) {
159 EXPECT_THAT(to_host<T>(lhs), ::testing::Pointwise(::testing::Eq(), to_host<T>(rhs)));
160 } else if constexpr (cuspatial::is_optional<T>) {
161 if constexpr (cuspatial::is_vec_2d<typename T::value_type>) {
162 EXPECT_THAT(to_host<T>(lhs),
163 ::testing::Pointwise(optional_matcher(vec_2d_matcher()), to_host<T>(rhs)));
164 } else if constexpr (std::is_floating_point_v<typename T::value_type>) {
165 EXPECT_THAT(to_host<T>(lhs),
166 ::testing::Pointwise(optional_matcher(float_matcher()), to_host<T>(rhs)));
167 } else if constexpr (std::is_integral_v<typename T::value_type>) {
168 EXPECT_THAT(to_host<T>(lhs),
169 ::testing::Pointwise(optional_matcher(::testing::Eq()), to_host<T>(rhs)));
170 } else {
171 EXPECT_EQ(lhs, rhs);
172 }
173 } else {
174 EXPECT_EQ(lhs, rhs);
175 }
176}
177
178template <typename Vector1, typename Vector2, typename U>
179inline void expect_vector_equivalent(Vector1 const& lhs, Vector2 const& rhs, U abs_error)
180{
181 using T = typename Vector1::value_type;
182 static_assert(std::is_same_v<T, typename Vector2::value_type>, "Value type mismatch.");
183 static_assert(!std::is_integral_v<T>, "Integral types cannot be compared with an error.");
184
185 if constexpr (cuspatial::is_vec_2d<T>) {
186 EXPECT_THAT(to_host<T>(lhs),
187 ::testing::Pointwise(vec_2d_near_matcher(abs_error), to_host<T>(rhs)));
188 } else if constexpr (std::is_floating_point_v<T>) {
189 EXPECT_THAT(to_host<T>(lhs),
190 ::testing::Pointwise(float_near_matcher(abs_error), to_host<T>(rhs)));
191 } else if constexpr (cuspatial::is_optional<T>) {
192 if constexpr (cuspatial::is_vec_2d<typename T::value_type>) {
193 EXPECT_THAT(to_host<T>(lhs),
194 ::testing::Pointwise(optional_matcher(vec_2d_matcher()), to_host<T>(rhs)));
195 } else if constexpr (std::is_floating_point_v<typename T::value_type>) {
196 EXPECT_THAT(to_host<T>(lhs),
197 ::testing::Pointwise(optional_matcher(float_matcher()), to_host<T>(rhs)));
198 } else {
199 EXPECT_EQ(lhs, rhs);
200 }
201 } else {
202 EXPECT_EQ(lhs, rhs);
203 }
204}
205
206#define CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs, rhs, ...) \
207 do { \
208 SCOPED_TRACE(" <-- line of failure\n"); \
209 cuspatial::test::expect_vector_equivalent(lhs, rhs, ##__VA_ARGS__); \
210 } while (0)
211
212// unpack a `device_vector of structs comprising two `vec_2d`s each into two vectors of `vec_2d`.
213// Works, e.g., with `cuspatial::box` or `cuspatial::segment`.
214template <typename PairVector, typename T = typename PairVector::value_type::value_type>
215std::pair<rmm::device_vector<vec_2d<T>>, rmm::device_vector<vec_2d<T>>> unpack_vec2d_pair_vector(
216 PairVector const& pairs)
217{
218 using Pair = typename PairVector::value_type;
219
220 auto first = rmm::device_vector<vec_2d<T>>(pairs.size());
221 auto second = rmm::device_vector<vec_2d<T>>(pairs.size());
222
223 auto zipped_output = thrust::make_zip_iterator(first.begin(), second.begin());
224
225 thrust::transform(pairs.begin(), pairs.end(), zipped_output, [] __device__(Pair const& pair) {
226 auto [a, b] = pair;
227 return thrust::make_tuple(a, b);
228 });
229 return {std::move(first), std::move(second)};
230}
231
232template <typename PairVector1, typename PairVector2>
233void expect_vec_2d_pair_equivalent(PairVector1 const& expected, PairVector2 const& got)
234{
235 auto [expected_first, expected_second] = unpack_vec2d_pair_vector(expected);
236 auto [got_first, got_second] = unpack_vec2d_pair_vector(got);
237 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(expected_first, got_first);
238 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(expected_second, got_second);
239}
240
241#define CUSPATIAL_EXPECT_VEC2D_PAIRS_EQUIVALENT(lhs, rhs) \
242 do { \
243 SCOPED_TRACE(" <-- line of failure\n"); \
244 cuspatial::test::expect_vec_2d_pair_equivalent(lhs, rhs); \
245 } while (0)
246
247template <typename Array1, typename Array2>
248void expect_multilinestring_array_equivalent(Array1& lhs, Array2& rhs)
249{
250 auto [lhs_geometry_offset, lhs_part_offset, lhs_coordinates] = lhs.release();
251 auto [rhs_geometry_offset, rhs_part_offset, rhs_coordinates] = rhs.release();
252
253 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs_geometry_offset, rhs_geometry_offset);
254 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs_part_offset, rhs_part_offset);
255 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs_coordinates, rhs_coordinates);
256}
257
258#define CUSPATIAL_EXPECT_MULTILINESTRING_ARRAY_EQUIVALENT(lhs, rhs) \
259 do { \
260 SCOPED_TRACE(" <-- line of failure\n"); \
261 cuspatial::test::expect_multilinestring_array_equivalent(lhs, rhs); \
262 } while (0)
263
264template <typename Array1, typename Array2>
265void expect_multipoint_array_equivalent(Array1& lhs, Array2& rhs)
266{
267 auto [lhs_geometry_offset, lhs_coordinates] = lhs.release();
268 auto [rhs_geometry_offset, rhs_coordinates] = rhs.release();
269
270 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs_geometry_offset, rhs_geometry_offset);
271 CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(lhs_coordinates, rhs_coordinates);
272}
273
274#define CUSPATIAL_EXPECT_MULTIPOINT_ARRAY_EQUIVALENT(lhs, rhs) \
275 do { \
276 SCOPED_TRACE(" <-- line of failure\n"); \
277 cuspatial::test::expect_multipoint_array_equivalent(lhs, rhs); \
278 } while (0)
279
280#define CUSPATIAL_RUN_TEST(FUNC, ...) \
281 do { \
282 SCOPED_TRACE(" <-- line of failure\n"); \
283 FUNC(__VA_ARGS__); \
284 } while (0)
285
286} // namespace test
287} // namespace cuspatial