From 1611bfe46b2bc3fc97193e3e89714de1c83a6cc9 Mon Sep 17 00:00:00 2001 From: serraramiro1 Date: Wed, 29 Mar 2023 16:56:19 -0300 Subject: [PATCH] Add support for per-axis resolution when clustering (#146) Fixes #68 `spatial_hash` now has a different resolution for each axis, which allows us to discriminate clustering resolution for each axis. Specialization for `Sophus::SE2d` now takes into account rotation for spatial clusterization. Signed-off-by: Ramiro Serra --- beluga/include/beluga/algorithm/sampling.hpp | 38 +++++---- .../include/beluga/algorithm/spatial_hash.hpp | 82 ++++++++++++++----- beluga/include/beluga/localization.hpp | 2 +- .../test/beluga/algorithm/test_sampling.cpp | 20 +++-- beluga/test/beluga/test_spatial_hash.cpp | 47 +++++++---- beluga/test/benchmark/benchmark_sampling.cpp | 10 +-- .../test/benchmark/benchmark_spatial_hash.cpp | 4 +- beluga_amcl/src/amcl_node.cpp | 36 +++++++- beluga_system_tests/test/test_system.cpp | 8 +- 9 files changed, 171 insertions(+), 76 deletions(-) diff --git a/beluga/include/beluga/algorithm/sampling.hpp b/beluga/include/beluga/algorithm/sampling.hpp index 2d0e6ef69..74bd5d11e 100644 --- a/beluga/include/beluga/algorithm/sampling.hpp +++ b/beluga/include/beluga/algorithm/sampling.hpp @@ -163,19 +163,19 @@ auto random_sample(const Range& samples, const Weights& weights, RandomNumberGen /// Returns a callable object that allows setting the cluster of a particle. /** - * \param resolution The size along any axis of the spatial cluster cell. - * \return A callable object with prototype `(ParticleT && p) -> ParticleT`. \n - * `ParticleT` must satisfy \ref ParticlePage. \n - * The expression \c particle_traits::cluster(p) must also - * be valid and return a `std::size_t &`. \n - * After the returned object is applied to a particle `p`, \c cluster(p) will be updated - * with the calculated spatial hash according to the specified resolution. + * \param hasher A copyable callable object with signature (const decltype(state(particle)) &) -> std::size_t, that + * returns a spatial cluster for a given particle state. + * \return A callable object with prototype `(ParticleT && p) ->ParticleT`. \n + * `ParticleT` must satisfy \ref ParticlePage. \ n + * The expression \c particle_traits::cluster(p) must also be valid and return a `std::size_t &`. \n + * After the returned object is applied to a particle `p`, \c cluster(p) will be updated with the calculated spatial + * hash according to the specified resolutions in each axis. */ -inline auto set_cluster(double resolution) { - return [resolution](auto&& particle) { - using state_type = std::decay_t; +template +inline auto set_cluster(Hasher&& hasher) { + return [hasher](auto&& particle) { auto new_particle = particle; - cluster(new_particle) = spatial_hash{}(state(particle), resolution); + cluster(new_particle) = hasher(state(particle)); return new_particle; }; } @@ -451,13 +451,17 @@ class FixedLimiter : public Mixin { }; /// Parameters used to construct a KldLimiter instance. +/** + * \tparam State Type that represents the state of the particle. + */ +template struct KldLimiterParam { /// Minimum number of particles to be sampled. std::size_t min_samples; /// Maximum number of particles to be sampled. std::size_t max_samples; - /// Cluster resolution in any axis, used to compute the spatial hash. - double spatial_resolution; + /// Hasher instance used to compute the spatial cluster for a given state. + spatial_hash spatial_hasher; /// See beluga::kld_condition() for details. double kld_epsilon; /// See beluga::kld_condition() for details. @@ -473,6 +477,8 @@ struct KldLimiterParam { * * \tparam Mixin The mixed-in type. `Mixin::self_type::particle_type` must exist and * satisfy \ref ParticlePage. + * \tparam State Type that represents the state of a particle. It should match with + * particle_traits::state_type. * * Additionally, given: * - `P`, the type `Mixin::self_type::particle_type` @@ -487,11 +493,11 @@ struct KldLimiterParam { * assigns the cluster hash to the particle `p`. \n * i.e. after the assignment `h` == \c particle_traits

::cluster(p) is true. */ -template +template class KldLimiter : public Mixin { public: /// Parameters type used to construct a KldLimiter instance. - using param_type = KldLimiterParam; + using param_type = KldLimiterParam; /// Constructs a KldLimiter instance. /** @@ -532,7 +538,7 @@ class KldLimiter : public Mixin { [[nodiscard]] auto take_samples() const { using particle_type = typename Mixin::self_type::particle_type; return ranges::views::transform(beluga::make_from_state) | - ranges::views::transform(beluga::set_cluster(parameters_.spatial_resolution)) | + ranges::views::transform(beluga::set_cluster(parameters_.spatial_hasher)) | ranges::views::take_while( beluga::kld_condition(parameters_.min_samples, parameters_.kld_epsilon, parameters_.kld_z), [](auto&& particle) { return cluster(particle); }) | diff --git a/beluga/include/beluga/algorithm/spatial_hash.hpp b/beluga/include/beluga/algorithm/spatial_hash.hpp index 1c525bedb..f5f0e93fb 100644 --- a/beluga/include/beluga/algorithm/spatial_hash.hpp +++ b/beluga/include/beluga/algorithm/spatial_hash.hpp @@ -63,10 +63,12 @@ constexpr std::size_t floor_and_shift(double value) { * \return The calculated hash. */ template -constexpr std::size_t -hash_impl(const T& value, double resolution, [[maybe_unused]] std::index_sequence index_sequence) { +constexpr std::size_t hash_impl( + const T& value, + const std::array& resolution, + [[maybe_unused]] std::index_sequence index_sequence) { constexpr auto kBits = std::numeric_limits::digits / sizeof...(Ids); - return (detail::floor_and_shift(std::get(value) / resolution) | ...); + return (detail::floor_and_shift(std::get(value) / resolution[Ids]) | ...); } } // namespace detail @@ -77,50 +79,92 @@ struct spatial_hash {}; /// Specialization for arrays. template -struct spatial_hash, std::enable_if_t, void>> { +class spatial_hash, std::enable_if_t, void>> { public: - /// Calculates the array hash, using the given resolution in all axes. + /// Type that represents the resolution in each axis. + using resolution_in_each_axis_t = std::array; + + /// Constructs a spatial hasher from an `std::array` of doubles. + /** + * \param resolution std::array of doubles containing resolution for each index of the array to be hashed, with + * matching indices. I.e: array[0] will be hashed with resolution[0]. + */ + explicit spatial_hash(const resolution_in_each_axis_t& resolution) : resolution_{resolution} {} + + /// Calculates the array hash, with the resolutions in each axis, given at construction time. /** * \param array Array to be hashed. - * \param resolution Resolution to be used in each axes. * \return The calculated hash. */ - constexpr std::size_t operator()(const std::array& array, double resolution = 1.) const { - return detail::hash_impl(array, resolution, std::make_index_sequence()); + constexpr std::size_t operator()(const std::array& array) const { + return detail::hash_impl(array, resolution_, std::make_index_sequence()); } + + private: + resolution_in_each_axis_t resolution_; }; /// Specialization for tuples. template