Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
motion_models.hpp
1 // Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
2 // Copyright (c) 2025 Open Navigation LLC
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #ifndef NAV2_MPPI_CONTROLLER__MOTION_MODELS_HPP_
17 #define NAV2_MPPI_CONTROLLER__MOTION_MODELS_HPP_
18 
19 #include <Eigen/Dense>
20 
21 #include <cstdint>
22 #include <string>
23 #include <algorithm>
24 #include <vector>
25 
26 #include "nav2_mppi_controller/models/control_sequence.hpp"
27 #include "nav2_mppi_controller/models/state.hpp"
28 #include "nav2_mppi_controller/models/constraints.hpp"
29 
30 #include "nav2_mppi_controller/tools/parameters_handler.hpp"
31 #include "nav2_util/geometry_utils.hpp"
32 #include "tf2/utils.hpp"
33 namespace mppi
34 {
35 
41 {
42 public:
46  MotionModel() = default;
47 
51  virtual ~MotionModel() = default;
52 
58  virtual void initialize(
59  ParametersHandler * /*param_handler*/,
60  const std::string & /*plugin_name*/)
61  {}
62 
69  const models::ControlConstraints & control_constraints, float model_dt,
70  float model_delay_vx, float model_delay_vy, float model_delay_wz, bool clamp_raw_controls)
71  {
72  control_constraints_ = control_constraints;
73  model_dt_ = model_dt;
74  model_delay_vx_ = model_delay_vx;
75  model_delay_vy_ = model_delay_vy;
76  model_delay_wz_ = model_delay_wz;
77  clamp_raw_controls_ = clamp_raw_controls;
78 
79  cmd_history_vx_.resize(offsetSteps(model_delay_vx_), 0.0f);
80  cmd_history_vy_.resize(offsetSteps(model_delay_vy_), 0.0f);
81  cmd_history_wz_.resize(offsetSteps(model_delay_wz_), 0.0f);
82  }
83 
88  void pushCommandHistory(float vx, float vy, float wz)
89  {
90  pushOne(cmd_history_vx_, vx);
91  pushOne(cmd_history_vy_, vy);
92  pushOne(cmd_history_wz_, wz);
93  }
94 
99  {
100  std::fill(cmd_history_vx_.begin(), cmd_history_vx_.end(), 0.0f);
101  std::fill(cmd_history_vy_.begin(), cmd_history_vy_.end(), 0.0f);
102  std::fill(cmd_history_wz_.begin(), cmd_history_wz_.end(), 0.0f);
103  }
104 
109  virtual void predict(models::State & state)
110  {
111  const bool is_holo = isHolonomic();
112  float max_delta_vx = model_dt_ * control_constraints_.ax_max;
113  float min_delta_vx = model_dt_ * control_constraints_.ax_min;
114  float max_delta_vy = model_dt_ * control_constraints_.ay_max;
115  float min_delta_vy = model_dt_ * control_constraints_.ay_min;
116  float max_delta_wz = model_dt_ * control_constraints_.az_max;
117  unsigned int n_cols = state.vx.cols();
118 
119  // Set dynamic limits to the platform velocities from the raw controls sampling
120  for (unsigned int i = 1; i < n_cols; i++) {
121  auto lower_bound_vx = (state.vx.col(i - 1) >
122  0).select(
123  state.vx.col(i - 1) + min_delta_vx,
124  state.vx.col(i - 1) - max_delta_vx);
125  auto upper_bound_vx = (state.vx.col(i - 1) >
126  0).select(
127  state.vx.col(i - 1) + max_delta_vx,
128  state.vx.col(i - 1) - min_delta_vx);
129  state.vx.col(i) = state.cvx.col(i - 1)
130  .cwiseMax(lower_bound_vx)
131  .cwiseMin(upper_bound_vx);
132  if (clamp_raw_controls_) {
133  state.cvx.col(i - 1) = state.vx.col(i);
134  }
135 
136  state.wz.col(i) = state.cwz.col(i - 1)
137  .cwiseMax(state.wz.col(i - 1) - max_delta_wz)
138  .cwiseMin(state.wz.col(i - 1) + max_delta_wz);
139  if (clamp_raw_controls_) {
140  state.cwz.col(i - 1) = state.wz.col(i);
141  }
142 
143  if (is_holo) {
144  auto lower_bound_vy = (state.vy.col(i - 1) >
145  0).select(
146  state.vy.col(i - 1) + min_delta_vy,
147  state.vy.col(i - 1) - max_delta_vy);
148  auto upper_bound_vy = (state.vy.col(i - 1) >
149  0).select(
150  state.vy.col(i - 1) + max_delta_vy,
151  state.vy.col(i - 1) - min_delta_vy);
152  state.vy.col(i) = state.cvy.col(i - 1)
153  .cwiseMax(lower_bound_vy)
154  .cwiseMin(upper_bound_vy);
155  if (clamp_raw_controls_) {
156  state.cvy.col(i - 1) = state.vy.col(i);
157  }
158  }
159  }
160 
161  const unsigned int offset_vx = std::floor((model_delay_vx_ / model_dt_) + 0.5);
162  const unsigned int offset_vy = std::floor((model_delay_vy_ / model_dt_) + 0.5);
163  const unsigned int offset_wz = std::floor((model_delay_wz_ / model_dt_) + 0.5);
164 
165  if (offset_vx > 0u || offset_wz > 0u || (is_holo && offset_vy > 0u)) {
166  applyDelayShift(state, is_holo, offset_vx, offset_vy, offset_wz);
167  }
168  }
169 
176  virtual void predictPose(
177  geometry_msgs::msg::Pose & pose,
178  const geometry_msgs::msg::Twist & speed, float pred_dt)
179  {
180  const bool is_holo = isHolonomic();
181  auto initial_yaw = static_cast<float>(tf2::getYaw(pose.orientation));
182  auto yaw_cos = cosf(initial_yaw);
183  auto yaw_sin = sinf(initial_yaw);
184  auto dx = static_cast<float>(speed.linear.x) * yaw_cos;
185  auto dy = static_cast<float>(speed.linear.x) * yaw_sin;
186  if (is_holo) {
187  dx -= speed.linear.y * yaw_sin;
188  dy += speed.linear.y * yaw_cos;
189  }
190  pose.position.x += dx * pred_dt;
191  pose.position.y += dy * pred_dt;
192  initial_yaw += speed.angular.z * pred_dt;
193  pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(initial_yaw);
194  }
199  virtual bool isHolonomic() const = 0;
200 
205  virtual void applyConstraints(models::ControlSequence & /*control_sequence*/) {}
206 
207 protected:
214  models::State & state, bool is_holo,
215  unsigned int offset_vx, unsigned int offset_vy, unsigned int offset_wz) const
216  {
217  auto shift = [](Eigen::ArrayXXf & velocities, unsigned int offset,
218  const std::vector<float> & history) {
219  const unsigned int cols = static_cast<unsigned int>(velocities.cols());
220  if (offset == 0u || cols == 0u) {
221  return;
222  }
223 
224  // Shift cols in-place right-to-left by offset
225  for (unsigned int k = (offset < cols) ? cols - offset : 0u; k > 0; --k) {
226  velocities.col(offset + k - 1) = velocities.col(k);
227  }
228 
229  // Fill delay window cols with the in-flight commands from history.
230  const unsigned int end = std::min(offset, cols);
231  for (unsigned int j = 1; j < end; ++j) {
232  velocities.col(j).setConstant(history[j]);
233  }
234  };
235 
236  shift(state.vx, offset_vx, cmd_history_vx_);
237  shift(state.wz, offset_wz, cmd_history_wz_);
238 
239  if (is_holo) {
240  shift(state.vy, offset_vy, cmd_history_vy_);
241  }
242  }
243 
247  std::size_t offsetSteps(float delay) const
248  {
249  if (delay <= 0.0f || model_dt_ <= 0.0f) {
250  return 0u;
251  }
252  return static_cast<std::size_t>(std::floor(delay / model_dt_ + 0.5f));
253  }
254 
258  static void pushOne(std::vector<float> & buf, float v)
259  {
260  if (buf.empty()) {return;}
261  std::rotate(buf.begin(), buf.begin() + 1, buf.end());
262  buf.back() = v;
263  }
264 
265  float model_dt_{0.0};
266  float model_delay_vx_{0.0};
267  float model_delay_vy_{0.0};
268  float model_delay_wz_{0.0};
269  bool clamp_raw_controls_{false};
270 
271  // Per-axis ring buffer of recently published commands
272  std::vector<float> cmd_history_vx_;
273  std::vector<float> cmd_history_vy_;
274  std::vector<float> cmd_history_wz_;
275 
276  models::ControlConstraints control_constraints_{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
277  0.0f, 0.0f};
278 };
279 
285 {
286 public:
290  AckermannMotionModel() = default;
291 
298  ParametersHandler * param_handler,
299  const std::string & plugin_name) override
300  {
301  auto getParam = param_handler->getParamGetter(plugin_name);
302  getParam(min_turning_r_, "min_turning_r", 0.2f);
303  }
304 
309  bool isHolonomic() const override
310  {
311  return false;
312  }
313 
318  void applyConstraints(models::ControlSequence & control_sequence) override
319  {
320  const auto wz_constrained = control_sequence.vx.abs() / min_turning_r_;
321  control_sequence.wz = control_sequence.wz
322  .max((-wz_constrained))
323  .min(wz_constrained);
324  }
329  float getMinTurningRadius() const {return min_turning_r_;}
330 
331 private:
332  float min_turning_r_{0.0f};
333 };
334 
340 {
341 public:
345  DiffDriveMotionModel() = default;
346 
351  bool isHolonomic() const override
352  {
353  return false;
354  }
355 };
356 
362 {
363 public:
367  OmniMotionModel() = default;
368 
373  bool isHolonomic() const override
374  {
375  return true;
376  }
377 };
378 
379 } // namespace mppi
380 
381 #endif // NAV2_MPPI_CONTROLLER__MOTION_MODELS_HPP_
Ackermann motion model.
void initialize(ParametersHandler *param_handler, const std::string &plugin_name) override
Initialize motion model.
AckermannMotionModel()=default
Constructor for mppi::AckermannMotionModel.
bool isHolonomic() const override
Whether the motion model is holonomic, using Y axis.
float getMinTurningRadius() const
Get minimum turning radius of ackermann drive.
void applyConstraints(models::ControlSequence &control_sequence) override
Apply hard vehicle constraints to a control sequence.
Differential drive motion model.
bool isHolonomic() const override
Whether the motion model is holonomic, using Y axis.
DiffDriveMotionModel()=default
Constructor for mppi::DiffDriveMotionModel.
Abstract pluginlib class for modeling a vehicle.
void applyDelayShift(models::State &state, bool is_holo, unsigned int offset_vx, unsigned int offset_vy, unsigned int offset_wz) const
Apply the per-axis input-delay shift to velocity rollout.
virtual bool isHolonomic() const =0
Whether the motion model is holonomic, using Y axis.
virtual void predictPose(geometry_msgs::msg::Pose &pose, const geometry_msgs::msg::Twist &speed, float pred_dt)
With input pose, speed find the vehicle's output pose at pred_dt in future.
virtual void initialize(ParametersHandler *, const std::string &)
Initialize motion model on bringup.
virtual void predict(models::State &state)
With input velocities, find the vehicle's output velocities.
virtual void applyConstraints(models::ControlSequence &)
Apply hard vehicle constraints to a control sequence.
void clearCommandHistory()
Zero the ring buffers.
void setConstraints(const models::ControlConstraints &control_constraints, float model_dt, float model_delay_vx, float model_delay_vy, float model_delay_wz, bool clamp_raw_controls)
Initialize motion model on bringup and set required variables.
std::size_t offsetSteps(float delay) const
Convert a delay in seconds to an offset in number of rollout steps, rounding to the nearest step.
MotionModel()=default
Constructor for mppi::MotionModel.
static void pushOne(std::vector< float > &buf, float v)
Push a value to the back of a ring buffer and rotate the elements.
virtual ~MotionModel()=default
Destructor for mppi::MotionModel.
void pushCommandHistory(float vx, float vy, float wz)
Push the most recently published command to the per-axis history ring buffers. Called once per contro...
Omnidirectional motion model.
OmniMotionModel()=default
Constructor for mppi::OmniMotionModel.
bool isHolonomic() const override
Whether the motion model is holonomic, using Y axis.
Handles getting parameters and dynamic parameter changes.
auto getParamGetter(const std::string &ns)
Get an object to retrieve parameters.
Constraints on control.
Definition: constraints.hpp:26
A control sequence over time (e.g. trajectory)
State information: velocities, controls, poses, speed.
Definition: state.hpp:32