Skip to content

Commit 72550b1

Browse files
add unir test to Ackerman motion model and update the interface of all models
Signed-off-by: fbattocchia <florencia.battochia@creativa77.com.ar>
1 parent 4f5d8d4 commit 72550b1

24 files changed

Lines changed: 522 additions & 110 deletions

beluga/include/beluga/algorithm/amcl_core.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,12 @@ class Amcl {
162162
* \return An optional pair containing the estimated pose and covariance after the update,
163163
* or std::nullopt if no update was performed.
164164
*/
165-
auto update(state_type control_action, measurement_type measurement) -> std::optional<estimation_type> {
165+
auto update(TimeStamped<state_type> control_action, measurement_type measurement) -> std::optional<estimation_type> {
166166
if (particles_.empty()) {
167167
return std::nullopt;
168168
}
169169

170-
if (!update_policy_(control_action) && !force_update_) {
170+
if (!update_policy_(control_action.value) && !force_update_) {
171171
return std::nullopt;
172172
}
173173

@@ -227,7 +227,7 @@ class Amcl {
227227

228228
random_state_generator_type random_state_generator_;
229229

230-
beluga::RollingWindow<state_type, 2> control_action_window_;
230+
beluga::RollingWindow<TimeStamped<state_type>, 2> control_action_window_;
231231

232232
bool force_update_{true};
233233
};

beluga/include/beluga/motion.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
#ifndef BELUGA_MOTION_HPP
1616
#define BELUGA_MOTION_HPP
1717

18+
#include <beluga/motion/ackerman_drive_model.hpp>
1819
#include <beluga/motion/differential_drive_model.hpp>
1920
#include <beluga/motion/omnidirectional_drive_model.hpp>
2021
#include <beluga/motion/stationary_model.hpp>
2122

2223
/**
2324
* \file
2425
* \brief Includes all Beluga motion models.
26+
*
27+
* Motion models in Beluga can be classified into two categories:
28+
* - Position-based models: Use pose differences (DifferentialDriveModel, OmnidirectionalDriveModel, StationaryModel)
29+
* - Velocity-based models: Use timestamped poses to calculate velocities (VelocityDriveModel)
2530
*/
2631

2732
/**
@@ -61,6 +66,7 @@
6166
* - beluga::DifferentialDriveModel
6267
* - beluga::OmnidirectionalDriveModel
6368
* - beluga::StationaryModel
69+
* - beluga::VelocityDriveModel
6470
*/
6571

6672
#endif

beluga/include/beluga/motion/ackerman_drive_model.hpp

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <tuple>
2222

2323
#include <beluga/type_traits/tuple_traits.hpp>
24+
#include <beluga/utility/time_stamped.hpp>
2425

2526
#include <beluga/3d_embedding.hpp>
2627
#include <sophus/se2.hpp>
@@ -39,33 +40,42 @@ namespace beluga {
3940
* See Probabilistic Robotics \cite thrun2005probabilistic Chapter 5.3, particularly table 5.3.
4041
*/
4142
struct VelocityDriveModelParam {
43+
/// Rotational noise from rotational velocity
44+
/**
45+
* How much rotational noise is generated by the rotational velocity.
46+
* Also known as `alpha1 in the differential drive model param`.
47+
*/
48+
double rotation_noise_from_rotation;
49+
/// Rotational noise from translation velocity
50+
/**
51+
* How much rotational noise is generated by the linear velocity.
52+
* Also known as `alpha2 in the differential drive model param`.
53+
*/
54+
double rotation_noise_from_translation;
4255
/// Translational noise from translation velocity
4356
/**
44-
* How much translational noise is generated by the translational velocity.
45-
* Also known as `alpha1`.
57+
* How much translational noise is generated by the linear velocity.
58+
* Also known as `alpha3 in the differential drive model param`.
4659
*/
4760
double translation_noise_from_translation;
4861
/// Translational noise from rotational velocity
4962
/**
5063
* How much translational noise is generated by the rotational velocity.
51-
* Also known as `alpha2`.
64+
* Also known as `alpha4 in the differential drive model param`.
5265
*/
5366
double translation_noise_from_rotation;
54-
/// Rotational noise from translation velocity
67+
/// Additional orientation noise from translational velocity
5568
/**
56-
* How much rotational noise is generated by the translational velocity.
57-
* Also known as `alpha3`.
69+
* How much extra orientation noise is generated by the linear velocity.
70+
* Also known as `alpha6`.
5871
*/
59-
double rotation_noise_from_translation;
60-
/// Rotational noise from rotational velocity
72+
double orientation_noise_from_translation;
73+
/// Additional orientation noise from rotational velocity
6174
/**
62-
* How much rotational noise is generated by the rotational velocity.
63-
* Also known as `alpha4`.
75+
* How much extra orientation noise is generated by the rotational velocity.
76+
* Also known as `alpha7`.
6477
*/
65-
double rotation_noise_from_rotation;
66-
67-
/// Distance threshold to detect in-place rotation.
68-
double distance_threshold = 0.01;
78+
double orientation_noise_from_rotation;
6979
};
7080

7181
/// Sampled velocity model for a differential drive.
@@ -88,10 +98,10 @@ class VelocityDriveModel {
8898
using state_type = StateType;
8999

90100
/// Time point type for motion model control actions.
91-
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
101+
using timestamped_state_type = TimeStamped<state_type>;
92102

93103
/// Current and previous pose estimates and time points as motion model control action.
94-
using control_type = std::tuple<state_type, state_type, time_point_type, time_point_type>;
104+
using control_type = std::tuple<timestamped_state_type, timestamped_state_type>;
95105

96106
/// Parameter type that the constructor uses to configure the motion model.
97107
using param_type = VelocityDriveModelParam;
@@ -111,7 +121,12 @@ class VelocityDriveModel {
111121
*/
112122
template <class Control, typename = common_tuple_type_t<Control, control_type>>
113123
[[nodiscard]] auto operator()(const Control& action) const {
114-
const auto& [pose, previous_pose, time, previous_time] = action;
124+
const auto& [timestamped, previous_timestamped] = action;
125+
const auto& pose = timestamped.value;
126+
const auto& previous_pose = previous_timestamped.value;
127+
128+
auto time = timestamped.timestamp;
129+
auto previous_time = previous_timestamped.timestamp;
115130
const auto delta_time = std::chrono::duration<double>(time - previous_time).count();
116131
if constexpr (std::is_same_v<state_type, Sophus::SE2d>) {
117132
return sampling_fn_2d(pose, previous_pose, delta_time);
@@ -121,8 +136,8 @@ class VelocityDriveModel {
121136
}
122137

123138
private:
124-
using control_type_2d = std::tuple<Sophus::SE2d, Sophus::SE2d, double>;
125-
using control_type_3d = std::tuple<Sophus::SE3d, Sophus::SE3d, double>;
139+
using control_type_2d = std::tuple<Sophus::SE2d, Sophus::SE2d>;
140+
using control_type_3d = std::tuple<Sophus::SE3d, Sophus::SE3d>;
126141

127142
[[nodiscard]] auto sampling_fn_3d(const Sophus::SE3d& pose, const Sophus::SE3d& previous_pose, double delta_time)
128143
const {
@@ -154,8 +169,8 @@ class VelocityDriveModel {
154169
const auto gamma_params = DistributionParam{
155170
0.0, // zero mean
156171
std::sqrt(
157-
params_.rotation_noise_from_translation * std::abs(linear_velocity) +
158-
params_.rotation_noise_from_rotation * std::abs(angular_velocity))};
172+
params_.orientation_noise_from_translation * std::abs(linear_velocity) +
173+
params_.orientation_noise_from_rotation * std::abs(angular_velocity))};
159174

160175
return [=](const auto& state, auto& gen) {
161176
static thread_local auto distribution = std::normal_distribution<double>{};
@@ -173,24 +188,38 @@ class VelocityDriveModel {
173188
/// Calculate linear and angular velocities from two poses and delta time
174189
std::pair<double, double>
175190
calculate_velocities(const Sophus::SE2d& pose, const Sophus::SE2d& previous_pose, double delta_time) const {
176-
// Distancia euclidiana
191+
// Euclidean distance (chord length between poses)
177192
const auto translation = pose.translation() - previous_pose.translation();
178-
const double distance = translation.norm();
193+
const double chord_distance = translation.norm();
179194

180-
// Velocidad angular
195+
// Angular velocity from orientation change
181196
const auto angular_change = pose.so2() * previous_pose.so2().inverse();
182197
const double angle_change = angular_change.log();
183198
const double angular_velocity = angle_change / delta_time;
184199

185-
// Velocidad lineal
200+
// Determine direction sign (forward/backward motion)
201+
const auto forward_direction =
202+
Eigen::Vector2d{std::cos(previous_pose.so2().log()), std::sin(previous_pose.so2().log())};
203+
const double dot_product = translation.dot(forward_direction);
204+
const double sign = (dot_product >= 0) ? 1.0 : -1.0;
205+
206+
// Linear velocity calculation
186207
double linear_velocity = 0.0;
208+
187209
if (std::abs(angle_change) > 1e-6) {
188-
// v = ω · r
189-
const double radius = distance / std::abs(angle_change);
190-
linear_velocity = std::abs(angular_velocity) * radius;
210+
// Circular motion: calculate radius from chord and angle
211+
// For an arc: chord = 2r·sin(θ/2), therefore r = chord / (2·sin(θ/2))
212+
const double radius = chord_distance / (2.0 * std::sin(std::abs(angle_change) / 2.0));
213+
214+
// Arc length: s = r · θ
215+
const double arc_distance = radius * std::abs(angle_change);
216+
217+
// Linear velocity with direction sign
218+
linear_velocity = sign * arc_distance / delta_time;
219+
191220
} else {
192-
// Movimiento rectilíneo: v = distancia/tiempo
193-
linear_velocity = distance / delta_time;
221+
// Straight line motion: v = distance / time
222+
linear_velocity = sign * chord_distance / delta_time;
194223
}
195224

196225
return {linear_velocity, angular_velocity};
@@ -206,7 +235,7 @@ class VelocityDriveModel {
206235

207236
Sophus::SE2d new_pose;
208237

209-
if (std::abs(omega_hat) < 1e-6) {
238+
if (std::abs(omega_hat) < 1e-4) {
210239
// Nearly straight line motion
211240
const auto translation =
212241
Eigen::Vector2d{v_hat * delta_time * std::cos(current_theta), v_hat * delta_time * std::sin(current_theta)};

beluga/include/beluga/motion/differential_drive_model.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <tuple>
2121

2222
#include <beluga/type_traits/tuple_traits.hpp>
23+
#include <beluga/utility/time_stamped.hpp>
2324

2425
#include <beluga/3d_embedding.hpp>
2526
#include <sophus/se2.hpp>
@@ -86,8 +87,11 @@ class DifferentialDriveModel {
8687
/// 2D or flattened 3D pose as motion model state (to match that of the particles).
8788
using state_type = StateType;
8889

90+
/// Time point type for motion model control actions.
91+
using timestamped_state_type = TimeStamped<state_type>;
92+
8993
/// Current and previous odometry estimates as motion model control action.
90-
using control_type = std::tuple<state_type, state_type>;
94+
using control_type = std::tuple<timestamped_state_type, timestamped_state_type>;
9195

9296
/// Parameter type that the constructor uses to configure the motion model.
9397
using param_type = DifferentialDriveModelParam;
@@ -107,7 +111,9 @@ class DifferentialDriveModel {
107111
*/
108112
template <class Control, typename = common_tuple_type_t<Control, control_type>>
109113
[[nodiscard]] auto operator()(const Control& action) const {
110-
const auto& [pose, previous_pose] = action;
114+
const auto& [timestamped, previous_timestamped] = action;
115+
const auto& pose = timestamped.value;
116+
const auto& previous_pose = previous_timestamped.value;
111117
if constexpr (std::is_same_v<state_type, Sophus::SE2d>) {
112118
return sampling_fn_2d(pose, previous_pose);
113119
} else {

beluga/include/beluga/motion/omnidirectional_drive_model.hpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <type_traits>
2222

2323
#include <beluga/type_traits/tuple_traits.hpp>
24+
#include <beluga/utility/time_stamped.hpp>
2425

2526
#include <sophus/se2.hpp>
2627
#include <sophus/so2.hpp>
@@ -77,11 +78,12 @@ struct OmnidirectionalDriveModelParam {
7778
*/
7879
class OmnidirectionalDriveModel {
7980
public:
80-
/// Current and previous odometry estimates as motion model control action.
81-
using control_type = std::tuple<Sophus::SE2d, Sophus::SE2d>;
8281
/// 2D pose as motion model state (to match that of the particles).
8382
using state_type = Sophus::SE2d;
84-
83+
/// Time point type for motion model control actions.
84+
using timestamped_state_type = TimeStamped<state_type>;
85+
/// Current and previous odometry estimates as motion model control action.
86+
using control_type = std::tuple<timestamped_state_type, timestamped_state_type>;
8587
/// Parameter type that the constructor uses to configure the motion model.
8688
using param_type = OmnidirectionalDriveModelParam;
8789

@@ -100,7 +102,9 @@ class OmnidirectionalDriveModel {
100102
*/
101103
template <class Control, typename = common_tuple_type_t<Control, control_type>>
102104
[[nodiscard]] auto operator()(Control&& action) const {
103-
const auto& [pose, previous_pose] = action;
105+
const auto& [timestamped, previous_timestamped] = action;
106+
const auto& pose = timestamped.value;
107+
const auto& previous_pose = previous_timestamped.value;
104108

105109
const auto translation = pose.translation() - previous_pose.translation();
106110
const double distance = translation.norm();

beluga/include/beluga/motion/stationary_model.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ namespace beluga {
3838
*/
3939
class StationaryModel {
4040
public:
41-
/// Current and previous odometry estimates as motion model control action.
42-
using control_type = std::tuple<Sophus::SE2d, Sophus::SE2d>;
4341
/// 2D pose as motion model state (to match that of the particles).
4442
using state_type = Sophus::SE2d;
45-
43+
/// Time point type for motion model control actions.
44+
using timestamped_state_type = TimeStamped<state_type>;
45+
/// Current and previous odometry estimates as motion model control action.
46+
using control_type = std::tuple<timestamped_state_type, timestamped_state_type>;
4647
/// Computes a state sampling function conditioned on a given control action.
4748
/**
4849
* The updated state will be centered around `state` with some covariance.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2023-2024 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_UTILITY_TIME_STAMPED_HPP
16+
#define BELUGA_UTILITY_TIME_STAMPED_HPP
17+
18+
#include <chrono>
19+
20+
/**
21+
* \file
22+
* \brief Implementation of a time-stamped wrapper for values.
23+
*/
24+
25+
namespace beluga {
26+
27+
/// A wrapper that associates a value with a timestamp.
28+
/**
29+
* This structure provides a way to bundle any value with its associated timestamp,
30+
* which is commonly needed in motion models and sensor processing where timing
31+
* information is crucial for proper calculations.
32+
*
33+
* \tparam T The type of the value to be time-stamped.
34+
* \tparam ClockT The clock type to use for timestamps. Defaults to system_clock.
35+
*/
36+
template <typename T, typename ClockT = std::chrono::system_clock>
37+
struct TimeStamped {
38+
/// The wrapped value.
39+
T value;
40+
41+
/// The timestamp associated with the value.
42+
std::chrono::time_point<ClockT> timestamp;
43+
/// Default constructor.
44+
/**
45+
* Initializes value with default construction and timestamp to epoch.
46+
*/
47+
TimeStamped() : value{}, timestamp{} {}
48+
49+
/// Constructs a TimeStamped with the current time.
50+
/**
51+
* \param val The value to be time-stamped.
52+
*/
53+
explicit TimeStamped(const T& val) : value(val), timestamp{} {}
54+
55+
/// Constructs a TimeStamped with a specific timestamp.
56+
/**
57+
* \param val The value to be time-stamped.
58+
* \param ts The timestamp to associate with the value.
59+
*/
60+
TimeStamped(const T& val, std::chrono::time_point<ClockT> ts) : value(val), timestamp(ts) {}
61+
};
62+
63+
/// Helper function to create a control action tuple with TimeStamped values
64+
template <typename T>
65+
auto make_control_action(const T& current, const T& previous) {
66+
return std::make_tuple(TimeStamped{current}, TimeStamped{previous});
67+
}
68+
69+
/// Helper function to create a control action tuple with TimeStamped values and explicit timestamps
70+
template <typename T, typename ClockT = std::chrono::system_clock>
71+
auto make_control_action(
72+
const T& current,
73+
const T& previous,
74+
std::chrono::time_point<ClockT> current_timestamp,
75+
std::chrono::time_point<ClockT> previous_timestamp) {
76+
return std::make_tuple(
77+
TimeStamped<T, ClockT>{current, current_timestamp}, TimeStamped<T, ClockT>{previous, previous_timestamp});
78+
}
79+
80+
} // namespace beluga
81+
82+
#endif

beluga/test/beluga/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_executable(
3333
algorithm/test_unscented_transform.cpp
3434
containers/test_circular_array.cpp
3535
containers/test_tuple_vector.cpp
36+
motion/test_ackerman_drive_model.cpp
3637
motion/test_differential_drive_model.cpp
3738
motion/test_omnidirectional_drive_model.cpp
3839
policies/test_every_n.cpp

0 commit comments

Comments
 (0)