test_omnidirectional_drive_model.cpp
Go to the documentation of this file.
1 // Copyright 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 #include <gmock/gmock.h>
16 #include <gtest/gtest.h>
17 
18 #include <cmath>
19 #include <functional>
20 #include <random>
21 #include <tuple>
22 #include <utility>
23 
24 #include <range/v3/view/common.hpp>
25 #include <range/v3/view/generate.hpp>
26 #include <range/v3/view/take_exactly.hpp>
27 
28 #include <Eigen/Core>
29 #include <sophus/common.hpp>
30 #include <sophus/se2.hpp>
31 #include <sophus/so2.hpp>
32 
35 
36 namespace {
37 
38 using Constants = Sophus::Constants<double>;
39 using Eigen::Vector2d;
40 using Sophus::SE2d;
41 using Sophus::SO2d;
42 
45 
46 class OmnidirectionalDriveModelTest : public ::testing::Test {
47  protected:
48  const UUT motion_model_{beluga::OmnidirectionalDriveModelParam{0.0, 0.0, 0.0, 0.0, 0.0}}; // No variance
49  std::mt19937 generator_{std::random_device()()};
50 };
51 
52 TEST_F(OmnidirectionalDriveModelTest, OneUpdate) {
53  constexpr double kTolerance = 0.001;
54  const auto control_action = std::make_tuple(
55  SE2d{SO2d{Constants::pi()}, Vector2d{1.0, -2.0}}, SE2d{SO2d{Constants::pi()}, Vector2d{1.0, -2.0}});
56  const auto state_sampling_function = motion_model_(control_action);
57  const auto pose = SE2d{SO2d{Constants::pi() / 3}, Vector2d{2.0, 5.0}};
58  ASSERT_THAT(state_sampling_function(pose, generator_), SE2Near(pose, kTolerance));
59 }
60 
61 TEST_F(OmnidirectionalDriveModelTest, Translate) {
62  constexpr double kTolerance = 0.001;
63  const auto control_action = std::make_tuple(SE2d{SO2d{0.0}, Vector2d{1.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
64  const auto state_sampling_function = motion_model_(control_action);
65  const auto result1 = state_sampling_function(SE2d{SO2d{0.0}, Vector2d{2.0, 0.0}}, generator_);
66  ASSERT_THAT(result1, SE2Near(SO2d{0.0}, Vector2d{3.0, 0.0}, kTolerance));
67  const auto result2 = state_sampling_function(SE2d{SO2d{0.0}, Vector2d{0.0, 3.0}}, generator_);
68  ASSERT_THAT(result2, SE2Near(SO2d{0.0}, Vector2d{1.0, 3.0}, kTolerance));
69 }
70 
71 TEST_F(OmnidirectionalDriveModelTest, RotateTranslate) {
72  constexpr double kTolerance = 0.001;
73  const auto control_action =
74  std::make_tuple(SE2d{SO2d{Constants::pi() / 2}, Vector2d{0.0, 1.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
75  const auto state_sampling_function = motion_model_(control_action);
76  const auto result1 = state_sampling_function(SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}}, generator_);
77  ASSERT_THAT(result1, SE2Near(SO2d{Constants::pi() / 2}, Vector2d{0.0, 1.0}, kTolerance));
78  const auto result2 = state_sampling_function(SE2d{SO2d{-Constants::pi() / 2}, Vector2d{2.0, 3.0}}, generator_);
79  ASSERT_THAT(result2, SE2Near(SO2d{0.0}, Vector2d{3.0, 3.0}, kTolerance));
80 }
81 
82 TEST_F(OmnidirectionalDriveModelTest, Rotate) {
83  constexpr double kTolerance = 0.001;
84  const auto control_action =
85  std::make_tuple(SE2d{SO2d{Constants::pi() / 4}, Vector2d{0.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
86  const auto state_sampling_function = motion_model_(control_action);
87  const auto result1 = state_sampling_function(SE2d{SO2d{Constants::pi()}, Vector2d{0.0, 0.0}}, generator_);
88  ASSERT_THAT(result1, SE2Near(SO2d{Constants::pi() * 5 / 4}, Vector2d{0.0, 0.0}, kTolerance));
89  const auto result2 = state_sampling_function(SE2d{SO2d{-Constants::pi() / 2}, Vector2d{0.0, 0.0}}, generator_);
90  ASSERT_THAT(result2, SE2Near(SO2d{-Constants::pi() / 4}, Vector2d{0.0, 0.0}, kTolerance));
91 }
92 
93 TEST_F(OmnidirectionalDriveModelTest, TranslateStrafe) {
94  constexpr double kTolerance = 0.001;
95  const auto control_action = std::make_tuple(SE2d{SO2d{0.0}, Vector2d{0.0, 1.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
96  const auto state_sampling_function = motion_model_(control_action);
97  const auto result1 = state_sampling_function(SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}}, generator_);
98  ASSERT_THAT(result1, SE2Near(SO2d{0.0}, Vector2d{0.0, 1.0}, kTolerance));
99 }
100 
101 template <class Range>
102 auto get_statistics(Range&& range) {
103  const auto size = static_cast<double>(std::distance(std::begin(range), std::end(range)));
104  const double sum = std::accumulate(std::begin(range), std::end(range), 0.0);
105  const double mean = sum / size;
106  const double squared_diff_sum =
107  std::transform_reduce(std::begin(range), std::end(range), 0.0, std::plus<>{}, [mean](double value) {
108  const double diff = value - mean;
109  return diff * diff;
110  });
111  const double stddev = std::sqrt(squared_diff_sum / size);
112  return std::pair{mean, stddev};
113 }
114 
115 TEST(OmnidirectionalDriveModelSamples, Translate) {
116  const double tolerance = 0.01;
117  const double alpha = 0.2;
118  const double origin = 5.0;
119  const double distance = 3.0;
120  const auto motion_model =
121  UUT{beluga::OmnidirectionalDriveModelParam{0.0, 0.0, alpha, 0.0, 0.0}}; // Translation variance
122  auto generator = std::mt19937{std::random_device()()};
123  const auto control_action =
124  std::make_tuple(SE2d{SO2d{0.0}, Vector2d{distance, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
125  const auto state_sampling_function = motion_model(control_action);
126  auto view = ranges::views::generate([&]() {
127  const auto pose = SE2d{SO2d{0.0}, Vector2d{origin, 0.0}};
128  return state_sampling_function(pose, generator).translation().x();
129  }) |
130  ranges::views::take_exactly(1'000'000) | ranges::views::common;
131  const auto [mean, stddev] = get_statistics(view);
132  ASSERT_NEAR(mean, origin + distance, tolerance);
133  ASSERT_NEAR(stddev, std::sqrt(alpha * distance * distance), tolerance);
134 }
135 
136 TEST(OmnidirectionalDriveModelSamples, RotateFirstQuadrant) {
137  const double tolerance = 0.01;
138  const double alpha = 0.2;
139  const double initial_angle = Constants::pi() / 6;
140  const double motion_angle = Constants::pi() / 4;
141  const auto motion_model = UUT{beluga::OmnidirectionalDriveModelParam{alpha, 0.0, 0.0, 0.0, 0.0}};
142  auto generator = std::mt19937{std::random_device()()};
143  const auto control_action =
144  std::make_tuple(SE2d{SO2d{motion_angle}, Vector2d{0.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
145  const auto state_sampling_function = motion_model(control_action);
146  auto view = ranges::views::generate([&]() {
147  const auto pose = SE2d{SO2d{initial_angle}, Vector2d{0.0, 0.0}};
148  return state_sampling_function(pose, generator).so2().log();
149  }) |
150  ranges::views::take_exactly(100'000) | ranges::views::common;
151  const auto [mean, stddev] = get_statistics(view);
152  ASSERT_NEAR(mean, initial_angle + motion_angle, tolerance);
153  ASSERT_NEAR(stddev, std::sqrt(alpha * motion_angle * motion_angle), tolerance);
154 }
155 
156 TEST(OmnidirectionalDriveModelSamples, RotateThirdQuadrant) {
157  const double tolerance = 0.01;
158  const double alpha = 0.2;
159  const double initial_angle = Constants::pi() / 6;
160  const double motion_angle = -Constants::pi() * 3 / 4;
161  const auto motion_model = UUT{beluga::OmnidirectionalDriveModelParam{alpha, 0.0, 0.0, 0.0, 0.0}};
162  auto generator = std::mt19937{std::random_device()()};
163  const auto control_action =
164  std::make_tuple(SE2d{SO2d{motion_angle}, Vector2d{0.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
165  const auto state_sampling_function = motion_model(control_action);
166  auto view = ranges::views::generate([&]() {
167  const auto pose = SE2d{SO2d{initial_angle}, Vector2d{0.0, 0.0}};
168  return state_sampling_function(pose, generator).so2().log();
169  }) |
170  ranges::views::take_exactly(100'000) | ranges::views::common;
171  const auto [mean, stddev] = get_statistics(view);
172  ASSERT_NEAR(mean, initial_angle + motion_angle, tolerance);
173 
174  // Treat backward and forward motion symmetrically for the noise models.
175  const double flipped_angle = Constants::pi() + motion_angle;
176  ASSERT_NEAR(stddev, std::sqrt(alpha * flipped_angle * flipped_angle), tolerance);
177 }
178 
179 } // namespace
kTolerance
double kTolerance
Definition: test_cluster_based_estimation.cpp:47
beluga::testing::SE2Near
auto SE2Near(const Sophus::SE2< Scalar > &t, Scalar e)
SE2 element matcher.
Definition: sophus_matchers.hpp:83
Sophus::SE2::so2
SOPHUS_FUNC SO2Member & so2()
beluga::UUT
beluga::BeamSensorModel< StaticOccupancyGrid< 5, 5 > > UUT
Definition: test_beam_model.cpp:27
omnidirectional_drive_model.hpp
Implementation of an omnidirectional drive odometry motion model.
se2.hpp
beluga::OmnidirectionalDriveModelParam
Parameters to construct an OmnidirectionalDriveModel instance.
Definition: omnidirectional_drive_model.hpp:36
common.hpp
Sophus::SE2
Vector2d
Vector2< double > Vector2d
so2.hpp
beluga::OmnidirectionalDriveModel
Sampled odometry model for an omnidirectional drive.
Definition: omnidirectional_drive_model.hpp:78
sophus_matchers.hpp
Implements GTest matchers for Sophus/Eigen types.
Sophus::Constants
Sophus::SE2::translation
SOPHUS_FUNC TranslationMember & translation()
Sophus::SE2d
SE2< double > SE2d
Sophus::SO2d
SO2< double > SO2d
beluga::TEST
TEST(Bresenham, MultiPassGuarantee)
Definition: test_bresenham.cpp:27


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