estimation.hpp
Go to the documentation of this file.
1 // Copyright 2022-2023 Ekumen, Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef BELUGA_ALGORITHM_ESTIMATION_HPP
16 #define BELUGA_ALGORITHM_ESTIMATION_HPP
17 
18 #include <range/v3/algorithm/count_if.hpp>
19 #include <range/v3/numeric/accumulate.hpp>
20 #include <range/v3/range/access.hpp>
21 #include <range/v3/range/primitives.hpp>
22 #include <range/v3/view/common.hpp>
23 #include <range/v3/view/repeat_n.hpp>
24 #include <range/v3/view/transform.hpp>
25 #include <sophus/se2.hpp>
26 #include <sophus/types.hpp>
27 
28 #include <numeric>
29 
36 namespace beluga {
37 
39 
51 template <class Range, class WeightsRange, class Scalar>
53 calculate_covariance(Range&& range, WeightsRange&& normalized_weights, const Sophus::Vector2<Scalar>& mean) {
54  auto translations_view = range | ranges::views::common;
55  auto normalized_weights_view = normalized_weights | ranges::views::common;
56 
57  auto calculate_weighted_sample_covariance = [mean](const auto& value, const auto& weight) {
58  const auto centered = value - mean;
60  weight * centered.x() * centered.x(), // weighted sample x autocovariance
61  weight * centered.x() * centered.y(), // weighted sample xy cross-covariance
62  weight * centered.y() * centered.y(), // weighted sample y autocovariance
63  };
64  };
65 
66  // calculate the averaging factor for the weighted covariance estimate
67  // See https://en.wikipedia.org/wiki/Sample_mean_and_covariance#Weighted_samples
68  const auto squared_weight_sum = std::transform_reduce(
69  normalized_weights_view.begin(), normalized_weights_view.end(), Scalar{0.0}, std::plus{},
70  [](const auto& weight) { return (weight * weight); });
71 
72  // calculate the x autocovariance, xy cross-covariance, and y autocovariance
73  Sophus::Vector3<Scalar> coefficients =
74  std::transform_reduce(
75  translations_view.begin(), translations_view.end(), normalized_weights_view.begin(),
76  Sophus::Vector3<Scalar>::Zero().eval(), std::plus{}, calculate_weighted_sample_covariance) /
77  (1.0 - squared_weight_sum);
78 
79  // create the symmetric 2x2 translation covariance matrix from the coefficients
80  auto covariance_matrix = Sophus::Matrix2<Scalar>{};
81  covariance_matrix << coefficients(0), coefficients(1), coefficients(1), coefficients(2);
82  return covariance_matrix;
83 }
84 
87 
96 template <class Range, class Scalar>
98  const auto sample_count = range.size();
99  return calculate_covariance(
100  range,
101  ranges::views::repeat_n(1.0 / static_cast<Scalar>(sample_count), static_cast<std::ptrdiff_t>(sample_count)),
102  mean);
103 }
104 
106 
123 template <
124  class Poses,
125  class Weights,
126  class Pose = ranges::range_value_t<Poses>,
127  class Scalar = typename Pose::Scalar,
128  typename = std::enable_if_t<std::is_same_v<Pose, typename Sophus::SE2<Scalar>>>>
129 std::pair<Sophus::SE2<Scalar>, Sophus::Matrix3<Scalar>> estimate(Poses&& poses, Weights&& weights) {
130  auto translation_view = poses | ranges::views::transform([](const auto& pose) { return pose.translation(); });
131  auto poses_view = poses | ranges::views::common;
132  auto weights_view = weights | ranges::views::common;
133  const auto weights_sum = std::accumulate(weights_view.begin(), weights_view.end(), 0.0);
134  auto normalized_weights_view =
135  weights_view | ranges::views::transform([weights_sum](const auto& weight) { return weight / weights_sum; });
136 
137  // map sophus pose 2D pose into a 4D Eigen vector. Mapping exploits that Sophus stores the 2D transform
138  // as two elements for the linear translation, and two more for the orientation (in complex number form)
139  const auto pose_to_weighted_eigen_vector = [](const auto& pose, const auto& weight) {
140  return Eigen::Map<const Sophus::Vector4<Scalar>>{pose.data()} * weight;
141  };
142 
143  // Compute the average of all the coefficients of the SE2 group elements and construct a new SE2 element. Notice
144  // that after averaging the complex representation of the orientation the resulting complex is not on the unit circle.
145  // This is expected and the value will be renormalized after having used the non-normal result to estimate the
146  // orientation autocovariance.
147  const Sophus::Vector4<Scalar> mean_pose_vector = std::transform_reduce(
148  poses_view.begin(), poses_view.end(), normalized_weights_view.begin(), Sophus::Vector4<Scalar>::Zero().eval(),
149  std::plus{}, pose_to_weighted_eigen_vector);
150 
151  // Calculate the weighted pose estimation
152  Sophus::SE2<Scalar> estimated_pose = Eigen::Map<const Sophus::SE2<Scalar>>{mean_pose_vector.data()};
153 
155 
156  // Compute the covariance of the translation part.
157  covariance_matrix.template topLeftCorner<2, 2>() =
158  calculate_covariance(translation_view, normalized_weights_view, estimated_pose.translation());
159 
160  // Compute the orientation variance and re-normalize the rotation component.
161  if (estimated_pose.so2().unit_complex().norm() < std::numeric_limits<double>::epsilon()) {
162  // Handle the case where both averages are too close to zero.
163  // Return zero yaw and infinite variance.
164  covariance_matrix.coeffRef(2, 2) = std::numeric_limits<double>::infinity();
165  estimated_pose.so2() = Sophus::SO2<Scalar>{0.0};
166  } else {
167  // See circular standard deviation in
168  // https://en.wikipedia.org/wiki/Directional_statistics#Dispersion.
169  covariance_matrix.coeffRef(2, 2) = -2.0 * std::log(estimated_pose.so2().unit_complex().norm());
170  estimated_pose.so2().normalize();
171  }
172  return std::pair{estimated_pose, covariance_matrix};
173 }
174 
176 
188 template <
189  class Scalars,
190  class Weights,
191  class Scalar = ranges::range_value_t<Scalars>,
192  typename = std::enable_if_t<std::is_arithmetic_v<Scalar>>>
193 std::pair<Scalar, Scalar> estimate(Scalars&& scalars, Weights&& weights) {
194  auto weights_view = weights | ranges::views::common;
195  const auto weights_sum = ranges::accumulate(weights, 0.0, std::plus<>{});
196  auto normalized_weights_view =
197  weights_view | ranges::views::transform([weights_sum](auto weight) { return weight / weights_sum; });
198 
199  const Scalar weighted_mean = std::transform_reduce(
200  scalars.begin(), scalars.end(), normalized_weights_view.begin(), 0.0, std::plus<>{}, std::multiplies<>{});
201 
202  const Scalar weighted_squared_deviations = std::transform_reduce(
203  scalars.begin(), scalars.end(), normalized_weights_view.begin(), 0.0, std::plus<>{},
204  [weighted_mean](const auto& scalar, const auto& weight) {
205  return weight * (scalar - weighted_mean) * (scalar - weighted_mean);
206  });
207 
208  const auto number_of_non_zero_weights =
209  static_cast<Scalar>(ranges::count_if(weights_view, [&](auto weight) { return weight > 0; }));
210 
211  const Scalar weighted_variance =
212  weighted_squared_deviations * number_of_non_zero_weights / (number_of_non_zero_weights - 1);
213 
214  return std::pair{weighted_mean, weighted_variance};
215 }
216 
218 
235 template <
236  class Poses,
237  class Pose = ranges::range_value_t<Poses>,
238  class Scalar = typename Pose::Scalar,
239  typename = std::enable_if_t<std::is_same_v<Pose, typename Sophus::SE2<Scalar>>>>
240 std::pair<Sophus::SE2<Scalar>, Sophus::Matrix3<Scalar>> estimate(Poses&& poses) {
241  return beluga::estimate(poses, ranges::views::repeat_n(1.0, static_cast<std::ptrdiff_t>(poses.size())));
242 }
243 
244 } // namespace beluga
245 
246 #endif
Sophus::SO2
Sophus::Vector3
Vector< Scalar, 3, Options > Vector3
Sophus::Matrix2
Matrix< Scalar, 2, 2 > Matrix2
beluga::weight
constexpr weight_detail::weight_fn weight
Customization point object for accessing the weight of a particle.
Definition: primitives.hpp:264
Sophus::Vector2
Vector< Scalar, 2, Options > Vector2
Sophus::SE2::so2
SOPHUS_FUNC SO2Member & so2()
se2.hpp
types.hpp
Sophus::SE2
Sophus::SE2::data
SOPHUS_FUNC Scalar * data()
beluga::estimate
std::pair< Sophus::SE2< Scalar >, Sophus::Matrix3< Scalar > > estimate(Poses &&poses, Weights &&weights)
Returns a pair consisting of the estimated mean pose and its covariance.
Definition: estimation.hpp:129
Sophus::Matrix3
Matrix< Scalar, 3, 3 > Matrix3
beluga::views::weights
constexpr auto weights
Definition: particles.hpp:34
Sophus::Vector4
Vector< Scalar, 4 > Vector4
Sophus::SE2::translation
SOPHUS_FUNC TranslationMember & translation()
beluga::calculate_covariance
Sophus::Matrix2< Scalar > calculate_covariance(Range &&range, WeightsRange &&normalized_weights, const Sophus::Vector2< Scalar > &mean)
Calculates the covariance of a range given its mean and the weights of each element.
Definition: estimation.hpp:53
beluga
The main Beluga namespace.
Definition: 3d_embedding.hpp:21
SO2< Scalar, Options >::unit_complex
SOPHUS_FUNC ComplexMember const & unit_complex() const


beluga
Author(s):
autogenerated on Tue Jul 16 2024 02:59:53