From 5124aa3f0e9899b1236eee619c055815e9fad835 Mon Sep 17 00:00:00 2001
From: Nahuel Espinosa <nespinosa@ekumenlabs.com>
Date: Fri, 12 Jul 2024 12:09:25 -0300
Subject: [PATCH] Address code review comments

Signed-off-by: Nahuel Espinosa <nespinosa@ekumenlabs.com>
---
 .../algorithm/cluster_based_estimation.hpp    | 55 ++++++++++---------
 .../test_cluster_based_estimation.cpp         | 21 ++++---
 2 files changed, 40 insertions(+), 36 deletions(-)

diff --git a/beluga/include/beluga/algorithm/cluster_based_estimation.hpp b/beluga/include/beluga/algorithm/cluster_based_estimation.hpp
index 67c95128f..2c60b5764 100644
--- a/beluga/include/beluga/algorithm/cluster_based_estimation.hpp
+++ b/beluga/include/beluga/algorithm/cluster_based_estimation.hpp
@@ -38,7 +38,6 @@
 // project
 #include <beluga/algorithm/estimation.hpp>
 #include <beluga/algorithm/spatial_hash.hpp>
-#include <beluga/views.hpp>
 
 #if RANGE_V3_MAJOR == 0 && RANGE_V3_MINOR < 12
 #include <range/v3/view/group_by.hpp>
@@ -53,6 +52,8 @@
 
 namespace beluga {
 
+namespace clusterizer_detail {
+
 /// Create a priority queue from a map using a specified projection.
 /**
  * This function template constructs a priority queue where the elements are
@@ -106,11 +107,9 @@ template <class Range>
   return values[static_cast<std::size_t>(n)];
 }
 
-namespace clusterizer_detail {
-
 /// A struct that holds the data of a single cell for the clusterization algorithm.
 template <class State>
-struct Cell {
+struct ClusterCell {
   State representative_state;             ///< state of a particle that is within the cell
   double weight{0.0};                     ///< average weight of the cell
   std::size_t num_particles{0};           ///< number of particles in the cell
@@ -119,7 +118,7 @@ struct Cell {
 
 /// A map that holds the sparse data about the particles grouped in cells.
 template <class State>
-using Map = std::unordered_map<std::size_t, Cell<State>>;
+using ClusterMap = std::unordered_map<std::size_t, ClusterCell<State>>;
 
 /// Create a cluster map from a range of particles and their corresponding spatial hashes.
 /**
@@ -138,14 +137,14 @@ using Map = std::unordered_map<std::size_t, Cell<State>>;
 template <class States, class Weights, class Hashes>
 [[nodiscard]] static auto make_cluster_map(States&& states, Weights&& weights, Hashes&& hashes) {
   using State = ranges::range_value_t<States>;
-  Map<State> map;
+  ClusterMap<State> map;
 
   // Preallocate memory with a very rough estimation of the number of cells we might end up with.
   map.reserve(states.size() / 5);
 
   // Calculate the accumulated cell weight and save a single representative state for each cell.
   for (const auto& [state, weight, hash] : ranges::views::zip(states, weights, hashes)) {
-    auto [it, inserted] = map.try_emplace(hash, Cell<State>{});
+    auto [it, inserted] = map.try_emplace(hash, ClusterCell<State>{});
     auto& [_, entry] = *it;
     entry.weight += weight;
     entry.num_particles++;
@@ -172,7 +171,7 @@ template <class States, class Weights, class Hashes>
  * \param percentile The percentile threshold for capping the weights.
  */
 template <class State>
-static void normalize_and_cap_weights(Map<State>& map, double percentile) {
+static void normalize_and_cap_weights(ClusterMap<State>& map, double percentile) {
   auto entries = ranges::views::values(map);
 
   for (auto& entry : entries) {
@@ -181,7 +180,7 @@ static void normalize_and_cap_weights(Map<State>& map, double percentile) {
   }
 
   const double max_weight =
-      calculate_percentile_threshold(ranges::views::transform(entries, &Cell<State>::weight), percentile);
+      calculate_percentile_threshold(ranges::views::transform(entries, &ClusterCell<State>::weight), percentile);
 
   for (auto& entry : entries) {
     entry.weight = std::min(entry.weight, max_weight);
@@ -201,8 +200,8 @@ static void normalize_and_cap_weights(Map<State>& map, double percentile) {
  * \param neighbors_function A function that returns neighboring cell hashes for a given state.
  */
 template <class State, class NeighborsFunction>
-static void assign_clusters(Map<State>& map, NeighborsFunction&& neighbors_function) {
-  auto queue = make_priority_queue(map, &Cell<State>::weight);
+static void assign_clusters(ClusterMap<State>& map, NeighborsFunction&& neighbors_function) {
+  auto queue = make_priority_queue(map, &ClusterCell<State>::weight);
   const auto max_priority = queue.top().priority;
 
   std::size_t next_cluster_id = 0;
@@ -284,14 +283,14 @@ class ParticleClusterizer {
 
   /// Clusters the given particles based on their states and weights.
   /**
-   * \tparam Particles The type of the particles range.
-   * \param particles A range of particles to be clustered.
+   * \tparam States Range type of the states.
+   * \tparam Weights Range type of the weights.
+   * \param states Range containing the states of the particles.
+   * \param weights Range containing the weights of the particles.
    * \return A vector of cluster IDs corresponding to the input particles.
    */
-  template <class Particles>
-  [[nodiscard]] auto operator()(Particles&& particles) {
-    auto states = beluga::views::states(particles);
-    auto weights = beluga::views::weights(particles);
+  template <class States, class Weights>
+  [[nodiscard]] auto operator()(States&& states, Weights&& weights) {
     auto hashes = states | ranges::views::transform(spatial_hash_function_) | ranges::to<std::vector>;
 
     auto map = clusterizer_detail::make_cluster_map(states, weights, hashes);
@@ -404,23 +403,25 @@ template <class States, class Weights, class Clusters>
  * of the cluster with the highest total weight is returned. If no clusters are found,
  * the overall mean and covariance of the particles are calculated and returned.
  *
- * \tparam Particles The type of the particles range.
- * \param particles A range of particles to be clustered and estimated.
+ * \tparam States Range type of the states.
+ * \tparam Weights Range type of the weights.
+ * \param states Range containing the states of the particles.
+ * \param weights Range containing the weights of the particles.
  * \param parameters Parameters for the particle clusterizer (optional).
  * \return A pair consisting of the state mean and covariance of the cluster with the highest total weight.
  */
-template <class Particles>
-[[nodiscard]] auto cluster_based_estimate(Particles&& particles, ParticleClusterizerParam parameters = {}) {
-  const auto clusters = ParticleClusterizer{parameters}(particles);
+template <class States, class Weights>
+[[nodiscard]] auto cluster_based_estimate(
+    States&& states,    //
+    Weights&& weights,  //
+    ParticleClusterizerParam parameters = {}) {
+  const auto clusters = ParticleClusterizer{parameters}(states, weights);
 
-  auto per_cluster_estimates =
-      estimate_clusters(beluga::views::states(particles), beluga::views::weights(particles), clusters);
+  auto per_cluster_estimates = estimate_clusters(states, weights, clusters);
 
   if (per_cluster_estimates.empty()) {
     // hmmm... maybe the particles are too fragmented? calculate the overall mean and covariance.
-    return beluga::estimate(
-        beluga::views::states(particles),  // namespace beluga
-        beluga::views::weights(particles));
+    return beluga::estimate(states, weights);
   }
 
   const auto [_, mean, covariance] =
diff --git a/beluga/test/beluga/algorithm/test_cluster_based_estimation.cpp b/beluga/test/beluga/algorithm/test_cluster_based_estimation.cpp
index 4d3a2ed49..64ab2c499 100644
--- a/beluga/test/beluga/algorithm/test_cluster_based_estimation.cpp
+++ b/beluga/test/beluga/algorithm/test_cluster_based_estimation.cpp
@@ -16,6 +16,7 @@
 
 #include <beluga/algorithm/cluster_based_estimation.hpp>
 #include <beluga/algorithm/spatial_hash.hpp>
+#include <beluga/views.hpp>
 #include <range/v3/action/sort.hpp>
 #include <range/v3/action/unique.hpp>
 #include <range/v3/view/filter.hpp>
@@ -55,11 +56,11 @@ struct ClusterBasedEstimationDetailTesting : public ::testing::Test {
   [[nodiscard]] auto generate_test_grid_cell_data_map() const {
     constexpr auto kUpperLimit = 30.0;
 
-    typename clusterizer_detail::Map<SE2d> map;
+    typename clusterizer_detail::ClusterMap<SE2d> map;
     for (double x = 0.0; x < kUpperLimit; x += 1.0) {
       const auto weight = x;
       const auto state = SE2d{SO2d{0.}, Vector2d{x, x}};
-      map.emplace(spatial_hash_function(state), clusterizer_detail::Cell<SE2d>{state, weight, 0, std::nullopt});
+      map.emplace(spatial_hash_function(state), clusterizer_detail::ClusterCell<SE2d>{state, weight, 0, std::nullopt});
     }
     return map;
   }
@@ -161,7 +162,7 @@ TEST_F(ClusterBasedEstimationDetailTesting, MakePriorityQueue) {
   auto data = generate_test_grid_cell_data_map();
 
   // test proper
-  auto prio_queue = make_priority_queue(data, &clusterizer_detail::Cell<SE2d>::weight);
+  auto prio_queue = make_priority_queue(data, &clusterizer_detail::ClusterCell<SE2d>::weight);
   EXPECT_EQ(prio_queue.size(), data.size());
 
   // from there on the weights should be strictly decreasing
@@ -187,11 +188,11 @@ TEST_F(ClusterBasedEstimationDetailTesting, MapGridCellsToClustersStep) {
     }
   }
 
-  typename clusterizer_detail::Map<SE2d> map;
+  typename clusterizer_detail::ClusterMap<SE2d> map;
 
   for (const auto& [x, y, w] : coordinates) {
     const auto state = SE2d{SO2d{0.}, Vector2d{x, y}};
-    map.emplace(spatial_hash_function(state), clusterizer_detail::Cell<SE2d>{state, w, 0, std::nullopt});
+    map.emplace(spatial_hash_function(state), clusterizer_detail::ClusterCell<SE2d>{state, w, 0, std::nullopt});
   }
 
   const auto neighbors = [&](const auto& state) {
@@ -212,7 +213,7 @@ TEST_F(ClusterBasedEstimationDetailTesting, MapGridCellsToClustersStep) {
   // only cells beyond the 10% weight percentile to avoid the messy border
   // between clusters beneath that threshold
   const auto ten_percent_threshold = calculate_percentile_threshold(
-      map | ranges::views::values | ranges::views::transform(&clusterizer_detail::Cell<SE2d>::weight), 0.15);
+      map | ranges::views::values | ranges::views::transform(&clusterizer_detail::ClusterCell<SE2d>::weight), 0.15);
 
   clusterizer_detail::assign_clusters(map, neighbors);
 
@@ -294,7 +295,8 @@ TEST_F(ClusterBasedEstimationDetailTesting, ClusterStateEstimationStep) {
   auto particles = make_particle_multicluster_dataset(0.0, k_field_side, 0.0, k_field_side, 1.0);
 
   const auto clusters =
-      ParticleClusterizer{ParticleClusterizerParam{kSpatialHashResolution, kAngularHashResolution, 0.9}}(particles);
+      ParticleClusterizer{ParticleClusterizerParam{kSpatialHashResolution, kAngularHashResolution, 0.9}}(
+          beluga::views::states(particles), beluga::views::weights(particles));
 
   auto per_cluster_estimates =
       estimate_clusters(beluga::views::states(particles), beluga::views::weights(particles), clusters);
@@ -369,7 +371,8 @@ TEST_F(ClusterBasedEstimationDetailTesting, HeaviestClusterSelectionTest) {
 
   const auto [expected_pose, expected_covariance] = beluga::estimate(max_peak_masked_states, max_peak_masked_weights);
 
-  const auto [pose, covariance] = beluga::cluster_based_estimate(particles);
+  const auto [pose, covariance] =
+      beluga::cluster_based_estimate(beluga::views::states(particles), beluga::views::weights(particles));
 
   ASSERT_THAT(pose, SE2Near(expected_pose.so2(), expected_pose.translation(), kTolerance));
   ASSERT_NEAR(covariance(0, 0), expected_covariance(0, 0), 0.001);
@@ -398,7 +401,7 @@ TEST_F(ClusterBasedEstimationDetailTesting, NightmareDistributionTest) {
   const auto [expected_pose, expected_covariance] = beluga::estimate(states, weights);
 
   auto particles = ranges::views::zip(states, weights) | ranges::to<std::vector>();
-  const auto [pose, covariance] = beluga::cluster_based_estimate(particles);
+  const auto [pose, covariance] = beluga::cluster_based_estimate(states, weights);
 
   ASSERT_THAT(pose, SE2Near(expected_pose.so2(), expected_pose.translation(), kTolerance));
   ASSERT_NEAR(covariance(0, 0), expected_covariance(0, 0), 0.001);