15 #include <gmock/gmock.h>
20 #include <range/v3/action/sort.hpp>
21 #include <range/v3/action/unique.hpp>
22 #include <range/v3/view/filter.hpp>
23 #include <range/v3/view/unique.hpp>
34 using Eigen::Vector2d;
39 template <
class Range,
class Hasher>
40 [[nodiscard]]
auto precalculate_particle_hashes(Range&&
states,
const Hasher& spatial_hash_function_) {
41 return states | ranges::views::transform(spatial_hash_function_) | ranges::to<std::vector<std::size_t>>();
44 struct ClusterBasedEstimationDetailTesting :
public ::testing::Test {
56 [[nodiscard]]
auto generate_test_grid_cell_data_map()
const {
57 constexpr
auto kUpperLimit = 30.0;
59 typename clusterizer_detail::ClusterMap<SE2d> map;
60 for (
double x = 0.0; x < kUpperLimit; x += 1.0) {
68 [[nodiscard]]
static auto
69 make_particle_multicluster_dataset(
double xmin,
double xmax,
double ymin,
double ymax,
double step) {
70 std::vector<std::pair<SE2d, beluga::Weight>> particles;
72 const auto xwidth = xmax - xmin;
73 const auto ywidth = ymax - ymin;
79 for (
double x = step / 2.0; x <= xwidth; x += step) {
80 for (
double y = step / 2.0; y <= ywidth; y += step) {
82 const auto k = (2 * x < xwidth ? 0.0 : 1.0) + (2 * y < ywidth ? 0.0 : 2.0) + 1.0;
83 auto weight = std::abs(std::sin(2.0 * M_PI * x / xwidth)) *
84 std::abs(std::sin(2.0 * M_PI * y / ywidth)) *
97 TEST_F(ClusterBasedEstimationDetailTesting, ParticleHashesCalculationStep) {
103 const auto states = std::vector{s00, s01, s10, s20};
112 ASSERT_EQ(hashes.size(), 4);
113 EXPECT_EQ(hashes[0], hash00);
114 EXPECT_EQ(hashes[1], hash01);
115 EXPECT_EQ(hashes[2], hash10);
116 EXPECT_EQ(hashes[3], hash20);
119 TEST_F(ClusterBasedEstimationDetailTesting, GridCellDataMapGenerationStep) {
125 const auto particles = std::vector<std::pair<SE2d, beluga::Weight>>{
126 std::make_pair(s00, 1.5),
127 std::make_pair(s01, 0.5),
128 std::make_pair(s10, 1.0),
129 std::make_pair(s20, 1.0),
142 ASSERT_EQ(test_data.size(), 3);
143 ASSERT_NE(test_data.find(hash00), test_data.end());
144 ASSERT_NE(test_data.find(hash10), test_data.end());
145 ASSERT_NE(test_data.find(hash20), test_data.end());
147 EXPECT_EQ(test_data[hash00].
weight, 2.0);
148 EXPECT_EQ(test_data[hash10].
weight, 1.0);
149 EXPECT_EQ(test_data[hash20].
weight, 1.0);
151 ASSERT_THAT(test_data[hash00].representative_state,
SE2Near(s00.so2(), s00.translation(),
kTolerance));
152 ASSERT_THAT(test_data[hash10].representative_state,
SE2Near(s10.so2(), s10.translation(),
kTolerance));
153 ASSERT_THAT(test_data[hash20].representative_state,
SE2Near(s20.so2(), s20.translation(),
kTolerance));
155 ASSERT_FALSE(test_data[hash00].cluster_id.has_value());
156 ASSERT_FALSE(test_data[hash10].cluster_id.has_value());
157 ASSERT_FALSE(test_data[hash20].cluster_id.has_value());
160 TEST_F(ClusterBasedEstimationDetailTesting, MakePriorityQueue) {
162 auto data = generate_test_grid_cell_data_map();
166 EXPECT_EQ(prio_queue.size(), data.size());
169 auto prev_weight = prio_queue.top().priority;
170 while (!prio_queue.empty()) {
171 const auto top = prio_queue.top();
172 EXPECT_GE(prev_weight, top.priority);
173 prev_weight = top.priority;
178 TEST_F(ClusterBasedEstimationDetailTesting, MapGridCellsToClustersStep) {
179 const double k_field_side = 36.0;
180 const double k_half_field_side = 18.0;
183 std::vector<std::tuple<double, double, double>> coordinates;
184 for (
double x = 0.0; x < k_field_side; x += 1.0) {
185 for (
double y = 0.0; y < k_field_side; y += 1.0) {
186 const auto weight = std::abs(std::sin(10.0 * x * M_PI / 180.0)) * std::abs(std::sin(10.0 * y * M_PI / 180.0));
187 coordinates.emplace_back(x, y,
weight);
191 typename clusterizer_detail::ClusterMap<SE2d> map;
193 for (
const auto& [x, y, w] : coordinates) {
198 const auto neighbors = [&](
const auto&
state) {
199 static const auto kAdjacentGridCells = {
206 return kAdjacentGridCells |
220 auto cells_above_minimum_threshold_view =
221 coordinates | ranges::views::filter([&](
const auto& c) {
return std::get<2>(c) >= ten_percent_threshold; });
223 const auto right_side_cell = [&](
const auto& c) {
return std::get<0>(c) >= k_half_field_side; };
224 const auto left_side_cell = [&](
const auto& c) {
return !right_side_cell(c); };
225 const auto top_side_cell = [&](
const auto& c) {
return std::get<1>(c) >= k_half_field_side; };
226 const auto bottom_side_cell = [&](
const auto& c) {
return !top_side_cell(c); };
228 auto quadrant_1_view = cells_above_minimum_threshold_view |
229 ranges::views::filter(left_side_cell) |
230 ranges::views::filter(bottom_side_cell);
231 auto quadrant_2_view = cells_above_minimum_threshold_view |
232 ranges::views::filter(right_side_cell) |
233 ranges::views::filter(bottom_side_cell);
234 auto quadrant_3_view = cells_above_minimum_threshold_view |
235 ranges::views::filter(left_side_cell) |
236 ranges::views::filter(top_side_cell);
237 auto quadrant_4_view = cells_above_minimum_threshold_view |
238 ranges::views::filter(right_side_cell) |
239 ranges::views::filter(top_side_cell);
241 const auto coord_to_hash = [&](
const auto& coords) {
242 const auto& [x, y, w] = coords;
247 const auto hash_to_id = [&](
const auto& hash) {
return map[hash].cluster_id.value(); };
249 auto quadrant_1_unique_ids = quadrant_1_view |
250 ranges::views::transform(coord_to_hash) |
251 ranges::views::transform(hash_to_id) |
252 ranges::to<std::vector<std::size_t>>() |
253 ranges::actions::sort |
254 ranges::actions::unique;
255 auto quadrant_2_unique_ids = quadrant_2_view |
256 ranges::views::transform(coord_to_hash) |
257 ranges::views::transform(hash_to_id) |
258 ranges::to<std::vector<std::size_t>>() |
259 ranges::actions::sort |
260 ranges::actions::unique;
261 auto quadrant_3_unique_ids = quadrant_3_view |
262 ranges::views::transform(coord_to_hash) |
263 ranges::views::transform(hash_to_id) |
264 ranges::to<std::vector<std::size_t>>() |
265 ranges::actions::sort |
266 ranges::actions::unique;
267 auto quadrant_4_unique_ids = quadrant_4_view |
268 ranges::views::transform(coord_to_hash) |
269 ranges::views::transform(hash_to_id) |
270 ranges::to<std::vector<std::size_t>>() |
271 ranges::actions::sort |
272 ranges::actions::unique;
274 auto full_field_unique_ids = cells_above_minimum_threshold_view |
275 ranges::views::transform(coord_to_hash) |
276 ranges::views::transform(hash_to_id) |
277 ranges::to<std::vector<std::size_t>>() |
278 ranges::actions::sort |
279 ranges::actions::unique;
283 EXPECT_EQ(quadrant_1_unique_ids.size(), 1);
284 EXPECT_EQ(quadrant_2_unique_ids.size(), 1);
285 EXPECT_EQ(quadrant_3_unique_ids.size(), 1);
286 EXPECT_EQ(quadrant_4_unique_ids.size(), 1);
288 EXPECT_EQ(full_field_unique_ids.size(), 4);
291 TEST_F(ClusterBasedEstimationDetailTesting, ClusterStateEstimationStep) {
292 const double k_field_side = 36.0;
295 auto particles = make_particle_multicluster_dataset(0.0, k_field_side, 0.0, k_field_side, 1.0);
297 const auto clusters =
301 auto per_cluster_estimates =
305 ASSERT_EQ(per_cluster_estimates.size(), 4);
308 ranges::sort(per_cluster_estimates, std::less{}, [](
const auto& e) {
return e.weight; });
317 TEST_F(ClusterBasedEstimationDetailTesting, ClusterEstimation) {
319 const auto states = std::vector{
331 const auto weights = std::vector{0.5, 0.5, 0.2, 0.3, 0.3, 0.2, 0.2, 1.0};
332 const auto clusters = std::vector{0, 0, 1, 2, 2, 1, 1, 3};
336 auto cluster_0_particles =
337 particles | ranges::views::cache1 | ranges::views::filter([](
const auto& p) {
return std::get<2>(p) == 0; });
339 auto cluster_0_states = cluster_0_particles | beluga::views::elements<0>;
340 auto cluster_0_weights = cluster_0_particles | beluga::views::elements<1>;
342 const auto [expected_pose, expected_covariance] =
beluga::estimate(cluster_0_states, cluster_0_weights);
345 ASSERT_EQ(per_cluster_estimates.size(), 3);
347 const auto [_, pose, covariance] =
348 *ranges::max_element(per_cluster_estimates, std::less{}, [](
const auto& e) {
return e.weight; });
352 ASSERT_THAT(pose,
SE2Near(expected_pose.so2(), expected_pose.translation(),
kTolerance));
358 TEST_F(ClusterBasedEstimationDetailTesting, HeaviestClusterSelectionTest) {
359 const auto particles = make_particle_multicluster_dataset(-2.0, +2.0, -2.0, +2.0, 0.025);
363 const auto max_peak_filter = [](
const auto& s) {
return s.translation().x() >= 0.0 && s.translation().y() >= 0.0; };
364 const auto mask_filter = [](
const auto&
sample) {
return std::get<1>(
sample); };
368 ranges::views::filter(mask_filter) | beluga::views::elements<0>;
370 ranges::views::filter(mask_filter) | beluga::views::elements<0>;
372 const auto [expected_pose, expected_covariance] =
beluga::estimate(max_peak_masked_states, max_peak_masked_weights);
374 const auto [pose, covariance] =
377 ASSERT_THAT(pose,
SE2Near(expected_pose.so2(), expected_pose.translation(),
kTolerance));
378 ASSERT_NEAR(covariance(0, 0), expected_covariance(0, 0), 0.001);
379 ASSERT_NEAR(covariance(0, 1), expected_covariance(0, 1), 0.001);
380 ASSERT_NEAR(covariance(0, 2), expected_covariance(0, 2), 0.001);
381 ASSERT_NEAR(covariance(1, 0), expected_covariance(1, 0), 0.001);
382 ASSERT_NEAR(covariance(1, 1), expected_covariance(1, 1), 0.001);
383 ASSERT_NEAR(covariance(1, 2), expected_covariance(1, 2), 0.001);
384 ASSERT_NEAR(covariance(2, 0), expected_covariance(2, 0), 0.001);
385 ASSERT_NEAR(covariance(2, 1), expected_covariance(2, 1), 0.001);
386 ASSERT_NEAR(covariance(2, 2), expected_covariance(2, 2), 0.001);
389 TEST_F(ClusterBasedEstimationDetailTesting, NightmareDistributionTest) {
392 const auto states = std::vector{
397 const auto weights = std::vector<beluga::Weight>{0.2, 0.2, 0.2, 0.2};
406 ASSERT_THAT(pose,
SE2Near(expected_pose.so2(), expected_pose.translation(),
kTolerance));
407 ASSERT_NEAR(covariance(0, 0), expected_covariance(0, 0), 0.001);
408 ASSERT_NEAR(covariance(0, 1), expected_covariance(0, 1), 0.001);
409 ASSERT_NEAR(covariance(0, 2), expected_covariance(0, 2), 0.001);
410 ASSERT_NEAR(covariance(1, 0), expected_covariance(1, 0), 0.001);
411 ASSERT_NEAR(covariance(1, 1), expected_covariance(1, 1), 0.001);
412 ASSERT_NEAR(covariance(1, 2), expected_covariance(1, 2), 0.001);
413 ASSERT_NEAR(covariance(2, 0), expected_covariance(2, 0), 0.001);
414 ASSERT_NEAR(covariance(2, 1), expected_covariance(2, 1), 0.001);
415 ASSERT_NEAR(covariance(2, 2), expected_covariance(2, 2), 0.001);