15 #include <gmock/gmock.h>
16 #include <gtest/gtest.h>
30 #include <range/v3/view/common.hpp>
31 #include <range/v3/view/generate.hpp>
32 #include <range/v3/view/take_exactly.hpp>
41 using Eigen::Vector2d;
49 class DifferentialDriveModelTest :
public ::testing::Test {
52 std::mt19937 generator_{std::random_device()()};
55 TEST_F(DifferentialDriveModelTest, OneUpdate) {
57 const auto control_action = std::make_tuple(
59 const auto state_sampling_function = motion_model_(control_action);
61 ASSERT_THAT(state_sampling_function(pose, generator_),
SE2Near(pose,
kTolerance));
64 TEST_F(DifferentialDriveModelTest, Translate) {
67 const auto state_sampling_function = motion_model_(control_action);
69 const auto result1 = state_sampling_function(
SE2d{
SO2d{0.0},
Vector2d{2.0, 0.0}}, generator_);
71 const auto result2 = state_sampling_function(
SE2d{
SO2d{0.0},
Vector2d{0.0, 3.0}}, generator_);
75 TEST_F(DifferentialDriveModelTest, RotateTranslate) {
77 const auto control_action =
79 const auto state_sampling_function = motion_model_(control_action);
81 const auto result1 = state_sampling_function(
SE2d{
SO2d{0.0},
Vector2d{0.0, 0.0}}, generator_);
83 const auto result2 = state_sampling_function(
SE2d{
SO2d{-Constants::pi() / 2},
Vector2d{2.0, 3.0}}, generator_);
87 TEST_F(DifferentialDriveModelTest, Rotate) {
89 const auto control_action =
91 const auto state_sampling_function = motion_model_(control_action);
92 const auto result1 = state_sampling_function(
SE2d{
SO2d{Constants::pi()},
Vector2d{0.0, 0.0}}, generator_);
94 const auto result2 = state_sampling_function(
SE2d{
SO2d{-Constants::pi() / 2},
Vector2d{0.0, 0.0}}, generator_);
98 TEST_F(DifferentialDriveModelTest, RotateTranslateRotate) {
100 const auto control_action =
102 const auto state_sampling_function = motion_model_(control_action);
107 template <
class Range>
108 auto get_statistics(Range&& range) {
109 const auto size =
static_cast<double>(std::distance(std::begin(range), std::end(range)));
110 const double sum = std::accumulate(std::begin(range), std::end(range), 0.0);
111 const double mean = sum / size;
112 const double squared_diff_sum =
113 std::transform_reduce(std::begin(range), std::end(range), 0.0, std::plus<>{}, [mean](
double value) {
114 const double diff = value - mean;
117 const double stddev = std::sqrt(squared_diff_sum / size);
118 return std::pair{mean, stddev};
121 TEST(DifferentialDriveModelSamples, Translate) {
122 const double tolerance = 0.015;
123 const double alpha = 0.2;
124 const double origin = 5.0;
125 const double distance = 3.0;
127 auto generator = std::mt19937{std::random_device()()};
128 const auto control_action =
130 const auto state_sampling_function = motion_model(control_action);
131 auto view = ranges::views::generate([&]() {
133 return state_sampling_function(pose, generator).
translation().x();
135 ranges::views::take_exactly(100
'000) | ranges::views::common;
136 const auto [mean, stddev] = get_statistics(view);
137 ASSERT_NEAR(mean, origin + distance, tolerance);
138 ASSERT_NEAR(stddev, std::sqrt(alpha * distance * distance), tolerance);
141 TEST(DifferentialDriveModelSamples, RotateFirstQuadrant) {
142 const double tolerance = 0.01;
143 const double alpha = 0.2;
144 const double initial_angle = Constants::pi() / 6;
145 const double motion_angle = Constants::pi() / 4;
146 const auto motion_model = UUT{beluga::DifferentialDriveModelParam{alpha, 0.0, 0.0, 0.0}}; // Rotation variance
147 auto generator = std::mt19937{std::random_device()()};
148 const auto control_action =
149 std::make_tuple(SE2d{SO2d{motion_angle}, Vector2d{0.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
150 const auto state_sampling_function = motion_model(control_action);
151 auto view = ranges::views::generate([&]() {
152 const auto pose = SE2d{SO2d{initial_angle}, Vector2d{0.0, 0.0}};
153 return state_sampling_function(pose, generator).so2().log();
155 ranges::views::take_exactly(100'000) | ranges::views::common;
156 const auto [mean, stddev] = get_statistics(view);
157 ASSERT_NEAR(mean, initial_angle + motion_angle, tolerance);
158 ASSERT_NEAR(stddev, std::sqrt(alpha * motion_angle * motion_angle), tolerance);
161 TEST(DifferentialDriveModel3DSamples, RotateFirstQuadrant) {
164 const double tolerance = 0.01;
165 const double alpha = 0.2;
166 const double initial_angle = Constants::pi() / 6;
167 const double motion_angle = Constants::pi() / 4;
170 auto generator = std::mt19937{std::random_device()()};
171 const auto control_action =
173 const auto state_sampling_function = motion_model(control_action);
174 auto view = ranges::views::generate([&]() {
176 return state_sampling_function(pose, generator).angleZ();
178 ranges::views::take_exactly(100
'000) | ranges::views::common;
179 const auto [mean, stddev] = get_statistics(view);
180 ASSERT_NEAR(mean, initial_angle + motion_angle, tolerance);
181 ASSERT_NEAR(stddev, std::sqrt(alpha * motion_angle * motion_angle), tolerance);
184 TEST(DifferentialDriveModelSamples, RotateThirdQuadrant) {
185 const double tolerance = 0.01;
186 const double alpha = 0.2;
187 const double initial_angle = Constants::pi() / 6;
188 const double motion_angle = -Constants::pi() * 3 / 4;
189 const auto motion_model = UUT{beluga::DifferentialDriveModelParam{alpha, 0.0, 0.0, 0.0}}; // Rotation variance
190 auto generator = std::mt19937{std::random_device()()};
191 const auto control_action =
192 std::make_tuple(SE2d{SO2d{motion_angle}, Vector2d{0.0, 0.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
193 const auto state_sampling_function = motion_model(control_action);
194 auto view = ranges::views::generate([&]() {
195 const auto pose = SE2d{SO2d{initial_angle}, Vector2d{0.0, 0.0}};
196 return state_sampling_function(pose, generator).so2().log();
198 ranges::views::take_exactly(100'000) | ranges::views::common;
199 const auto [mean, stddev] = get_statistics(view);
200 ASSERT_NEAR(mean, initial_angle + motion_angle, tolerance);
203 const double flipped_angle = Constants::pi() + motion_angle;
204 ASSERT_NEAR(stddev, std::sqrt(alpha * flipped_angle * flipped_angle), tolerance);
207 TEST(DifferentialDriveModelSamples, RotateTranslateRotateFirstQuadrant) {
208 const double tolerance = 0.01;
209 const double alpha = 0.2;
210 const auto motion_model =
212 auto generator = std::mt19937{std::random_device()()};
214 const auto state_sampling_function = motion_model(control_action);
215 auto view = ranges::views::generate([&]() {
217 return state_sampling_function(pose, generator).
translation().norm();
219 ranges::views::take_exactly(100
'000) | ranges::views::common;
220 const auto [mean, stddev] = get_statistics(view);
221 ASSERT_NEAR(mean, 1.41, tolerance);
223 // Net rotation is zero comparing final and initial poses, but
224 // the model requires a 45 degree counter-clockwise rotation,
225 // a forward translation, and a 45 degree clockwise rotation.
226 const double first_rotation = Constants::pi() / 4;
227 const double second_rotation = first_rotation;
228 const double rotation_variance = (first_rotation * first_rotation) + (second_rotation * second_rotation);
229 ASSERT_NEAR(stddev, std::sqrt(alpha * rotation_variance), tolerance);
232 TEST(DifferentialDriveModelSamples, RotateTranslateRotateThirdQuadrant) {
233 const double tolerance = 0.01;
234 const double alpha = 0.2;
235 const auto motion_model =
236 UUT{beluga::DifferentialDriveModelParam{0.0, 0.0, 0.0, alpha}}; // Translation variance from rotation
237 auto generator = std::mt19937{std::random_device()()};
238 const auto control_action =
239 std::make_tuple(SE2d{SO2d{0.0}, Vector2d{-1.0, -1.0}}, SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}});
240 const auto state_sampling_function = motion_model(control_action);
241 auto view = ranges::views::generate([&]() {
242 const auto pose = SE2d{SO2d{0.0}, Vector2d{0.0, 0.0}};
243 return state_sampling_function(pose, generator).translation().norm();
245 ranges::views::take_exactly(100'000) | ranges::views::common;
246 const auto [mean, stddev] = get_statistics(view);
247 ASSERT_NEAR(mean, 1.41, 0.01);
252 const double first_rotation = Constants::pi() / 4;
253 const double second_rotation = first_rotation;
254 const double rotation_variance = (first_rotation * first_rotation) + (second_rotation * second_rotation);
255 ASSERT_NEAR(stddev, std::sqrt(alpha * rotation_variance), tolerance);