15 #ifndef BELUGA_ALGORITHM_CLUSTER_BASED_ESTIMATION_HPP
16 #define BELUGA_ALGORITHM_CLUSTER_BASED_ESTIMATION_HPP
22 #include <unordered_map>
27 #include <range/v3/algorithm/max_element.hpp>
28 #include <range/v3/algorithm/sort.hpp>
29 #include <range/v3/numeric/accumulate.hpp>
30 #include <range/v3/range/conversion.hpp>
31 #include <range/v3/view/cache1.hpp>
32 #include <range/v3/view/filter.hpp>
33 #include <range/v3/view/map.hpp>
34 #include <range/v3/view/zip.hpp>
42 #if RANGE_V3_MAJOR == 0 && RANGE_V3_MINOR < 12
43 #include <range/v3/view/group_by.hpp>
45 #include <range/v3/view/chunk_by.hpp>
55 namespace clusterizer_detail {
71 template <
class Map,
class Proj>
73 struct KeyWithPriority {
75 typename Map::key_type key;
77 bool operator<(
const KeyWithPriority& other)
const {
78 return priority < other.priority;
82 const auto make_from_pair = [
proj = std::forward<Proj>(
proj)](
const auto& pair) {
83 return KeyWithPriority{std::invoke(
proj, pair.second), pair.first};
86 const auto range = map |
87 ranges::views::transform(make_from_pair) |
88 ranges::views::common;
90 return std::priority_queue<KeyWithPriority>(range.begin(), range.end());
102 template <
class Range>
104 auto values = range | ranges::to<std::vector>;
105 const auto n =
static_cast<std::ptrdiff_t
>(
static_cast<double>(values.size()) * percentile);
106 std::nth_element(values.begin(), values.begin() + n, values.end());
107 return values[
static_cast<std::size_t
>(n)];
111 template <
class State>
120 template <
class State>
121 using ClusterMap = std::unordered_map<std::size_t, ClusterCell<State>>;
137 template <
class States,
class Weights,
class Hashes>
139 using State = ranges::range_value_t<States>;
143 map.reserve(
states.size() / 5);
148 auto& [_, entry] = *it;
150 entry.num_particles++;
152 entry.representative_state =
state;
173 template <
class State>
175 auto entries = ranges::views::values(map);
177 for (
auto& entry : entries) {
178 assert(entry.num_particles > 0);
179 entry.weight /=
static_cast<double>(entry.num_particles);
182 const double max_weight =
185 for (
auto& entry : entries) {
186 entry.weight = std::min(entry.weight, max_weight);
202 template <
class State,
class NeighborsFunction>
205 const auto max_priority = queue.top().priority;
207 std::size_t next_cluster_id = 0;
210 while (!queue.empty()) {
211 const auto hash = queue.top().key;
215 auto& cell = map[hash];
218 if (!cell.cluster_id.has_value()) {
219 cell.cluster_id = next_cluster_id++;
228 const auto is_valid_neighbor = [&](
const auto& neighbor_hash) {
229 auto it = map.find(neighbor_hash);
232 (!it->second.cluster_id.has_value()) &&
233 (it->second.weight <= cell.weight));
237 for (
const std::size_t neighbor_hash : neighbors_function(cell.representative_state) |
238 ranges::views::cache1 |
239 ranges::views::filter(is_valid_neighbor)) {
240 auto& neighbor = map[neighbor_hash];
241 neighbor.cluster_id = cell.cluster_id;
242 queue.push({max_priority + neighbor.weight, neighbor_hash});
280 ranges::views::transform([&pose](
const Sophus::SE2d& neighbor_pose) {
return pose * neighbor_pose; }) |
292 template <
class States,
class Weights>
301 ranges::views::transform([&map](std::size_t hash) {
return map[hash].cluster_id.value(); }) |
302 ranges::to<std::vector>;
335 template <
class States,
class Weights,
class Clusters>
337 using State =
typename ranges::range_value_t<States>;
338 using Weight =
typename ranges::range_value_t<Weights>;
339 using Cluster =
typename ranges::range_value_t<Clusters>;
344 static_assert(std::is_same_v<State, EstimateState>);
352 static constexpr
auto create(
const State& s,
Weight w,
Cluster c) {
return Particle{s, w, c}; }
358 EstimateCovariance covariance;
361 auto particles = ranges::views::zip_with(&Particle::create,
states,
weights, clusters) |
362 ranges::to<std::vector>;
364 ranges::sort(particles, std::less{}, &Particle::cluster);
368 #if RANGE_V3_MAJOR == 0 && RANGE_V3_MINOR < 12
377 ranges::views::group_by([](
const auto& p1,
const auto& p2) {
return p1.cluster == p2.cluster; }) |
379 ranges::views::chunk_by([](
const auto& p1,
const auto& p2) {
return p1.cluster == p2.cluster; }) |
381 ranges::views::cache1 |
382 ranges::views::filter([](
auto subrange) {
383 #if RANGE_V3_MAJOR == 0 && RANGE_V3_MINOR < 11
384 return ranges::distance(subrange) > 1;
387 return subrange.size() > 1;
390 ranges::views::transform([](
auto subrange) {
394 const auto total_weight = ranges::accumulate(
weights, 0.0);
395 return Estimate{total_weight, std::move(mean), std::move(covariance)};
397 ranges::to<std::vector>;
413 template <
class States,
class Weights>
418 const auto clusters = ParticleClusterizer{parameters}(
states,
weights);
422 if (per_cluster_estimates.empty()) {
427 const auto [_, mean, covariance] =
428 *ranges::max_element(per_cluster_estimates, std::less{}, [](
const auto& e) {
return e.weight; });
430 return std::make_pair(mean, covariance);