Nav2 Navigation Stack - lyrical  lyrical
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 
32 namespace mppi
33 {
34 
40 {
41 public:
45  MotionModel() = default;
46 
50  virtual ~MotionModel() = default;
51 
57  virtual void initialize(
58  ParametersHandler * /*param_handler*/,
59  const std::string & /*plugin_name*/)
60  {}
61 
68  const models::ControlConstraints & control_constraints, float model_dt,
69  float model_delay_vx, float model_delay_vy, float model_delay_wz, bool clamp_raw_controls)
70  {
71  control_constraints_ = control_constraints;
72  model_dt_ = model_dt;
73  model_delay_vx_ = model_delay_vx;
74  model_delay_vy_ = model_delay_vy;
75  model_delay_wz_ = model_delay_wz;
76  clamp_raw_controls_ = clamp_raw_controls;
77 
78  cmd_history_vx_.resize(offsetSteps(model_delay_vx_), 0.0f);
79  cmd_history_vy_.resize(offsetSteps(model_delay_vy_), 0.0f);
80  cmd_history_wz_.resize(offsetSteps(model_delay_wz_), 0.0f);
81  }
82 
87  void pushCommandHistory(float vx, float vy, float wz)
88  {
89  pushOne(cmd_history_vx_, vx);
90  pushOne(cmd_history_vy_, vy);
91  pushOne(cmd_history_wz_, wz);
92  }
93 
98  {
99  std::fill(cmd_history_vx_.begin(), cmd_history_vx_.end(), 0.0f);
100  std::fill(cmd_history_vy_.begin(), cmd_history_vy_.end(), 0.0f);
101  std::fill(cmd_history_wz_.begin(), cmd_history_wz_.end(), 0.0f);
102  }
103 
108  virtual void predict(models::State & state)
109  {
110  const bool is_holo = isHolonomic();
111  float max_delta_vx = model_dt_ * control_constraints_.ax_max;
112  float min_delta_vx = model_dt_ * control_constraints_.ax_min;
113  float max_delta_vy = model_dt_ * control_constraints_.ay_max;
114  float min_delta_vy = model_dt_ * control_constraints_.ay_min;
115  float max_delta_wz = model_dt_ * control_constraints_.az_max;
116  unsigned int n_cols = state.vx.cols();
117 
118  // Set dynamic limits to the platform velocities from the raw controls sampling
119  for (unsigned int i = 1; i < n_cols; i++) {
120  auto lower_bound_vx = (state.vx.col(i - 1) >
121  0).select(
122  state.vx.col(i - 1) + min_delta_vx,
123  state.vx.col(i - 1) - max_delta_vx);
124  auto upper_bound_vx = (state.vx.col(i - 1) >
125  0).select(
126  state.vx.col(i - 1) + max_delta_vx,
127  state.vx.col(i - 1) - min_delta_vx);
128  state.vx.col(i) = state.cvx.col(i - 1)
129  .cwiseMax(lower_bound_vx)
130  .cwiseMin(upper_bound_vx);
131  if (clamp_raw_controls_) {
132  state.cvx.col(i - 1) = state.vx.col(i);
133  }
134 
135  state.wz.col(i) = state.cwz.col(i - 1)
136  .cwiseMax(state.wz.col(i - 1) - max_delta_wz)
137  .cwiseMin(state.wz.col(i - 1) + max_delta_wz);
138  if (clamp_raw_controls_) {
139  state.cwz.col(i - 1) = state.wz.col(i);
140  }
141 
142  if (is_holo) {
143  auto lower_bound_vy = (state.vy.col(i - 1) >
144  0).select(
145  state.vy.col(i - 1) + min_delta_vy,
146  state.vy.col(i - 1) - max_delta_vy);
147  auto upper_bound_vy = (state.vy.col(i - 1) >
148  0).select(
149  state.vy.col(i - 1) + max_delta_vy,
150  state.vy.col(i - 1) - min_delta_vy);
151  state.vy.col(i) = state.cvy.col(i - 1)
152  .cwiseMax(lower_bound_vy)
153  .cwiseMin(upper_bound_vy);
154  if (clamp_raw_controls_) {
155  state.cvy.col(i - 1) = state.vy.col(i);
156  }
157  }
158  }
159 
160  const unsigned int offset_vx = std::floor((model_delay_vx_ / model_dt_) + 0.5);
161  const unsigned int offset_vy = std::floor((model_delay_vy_ / model_dt_) + 0.5);
162  const unsigned int offset_wz = std::floor((model_delay_wz_ / model_dt_) + 0.5);
163 
164  if (offset_vx > 0u || offset_wz > 0u || (is_holo && offset_vy > 0u)) {
165  applyDelayShift(state, is_holo, offset_vx, offset_vy, offset_wz);
166  }
167  }
168 
173  virtual bool isHolonomic() const = 0;
174 
179  virtual void applyConstraints(models::ControlSequence & /*control_sequence*/) {}
180 
181 protected:
188  models::State & state, bool is_holo,
189  unsigned int offset_vx, unsigned int offset_vy, unsigned int offset_wz) const
190  {
191  auto shift = [](Eigen::ArrayXXf & velocities, unsigned int offset,
192  const std::vector<float> & history) {
193  const unsigned int cols = static_cast<unsigned int>(velocities.cols());
194  if (offset == 0u || cols == 0u) {
195  return;
196  }
197 
198  // Shift cols in-place right-to-left by offset
199  for (unsigned int k = (offset < cols) ? cols - offset : 0u; k > 0; --k) {
200  velocities.col(offset + k - 1) = velocities.col(k);
201  }
202 
203  // Fill delay window cols with the in-flight commands from history.
204  const unsigned int end = std::min(offset, cols);
205  for (unsigned int j = 1; j < end; ++j) {
206  velocities.col(j).setConstant(history[j]);
207  }
208  };
209 
210  shift(state.vx, offset_vx, cmd_history_vx_);
211  shift(state.wz, offset_wz, cmd_history_wz_);
212 
213  if (is_holo) {
214  shift(state.vy, offset_vy, cmd_history_vy_);
215  }
216  }
217 
221  std::size_t offsetSteps(float delay) const
222  {
223  if (delay <= 0.0f || model_dt_ <= 0.0f) {
224  return 0u;
225  }
226  return static_cast<std::size_t>(std::floor(delay / model_dt_ + 0.5f));
227  }
228 
232  static void pushOne(std::vector<float> & buf, float v)
233  {
234  if (buf.empty()) {return;}
235  std::rotate(buf.begin(), buf.begin() + 1, buf.end());
236  buf.back() = v;
237  }
238 
239  float model_dt_{0.0};
240  float model_delay_vx_{0.0};
241  float model_delay_vy_{0.0};
242  float model_delay_wz_{0.0};
243  bool clamp_raw_controls_{false};
244 
245  // Per-axis ring buffer of recently published commands
246  std::vector<float> cmd_history_vx_;
247  std::vector<float> cmd_history_vy_;
248  std::vector<float> cmd_history_wz_;
249 
250  models::ControlConstraints control_constraints_{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
251  0.0f, 0.0f};
252 };
253 
259 {
260 public:
264  AckermannMotionModel() = default;
265 
272  ParametersHandler * param_handler,
273  const std::string & plugin_name) override
274  {
275  auto getParam = param_handler->getParamGetter(plugin_name);
276  getParam(min_turning_r_, "min_turning_r", 0.2f);
277  }
278 
283  bool isHolonomic() const override
284  {
285  return false;
286  }
287 
292  void applyConstraints(models::ControlSequence & control_sequence) override
293  {
294  const auto wz_constrained = control_sequence.vx.abs() / min_turning_r_;
295  control_sequence.wz = control_sequence.wz
296  .max((-wz_constrained))
297  .min(wz_constrained);
298  }
303  float getMinTurningRadius() const {return min_turning_r_;}
304 
305 private:
306  float min_turning_r_{0.0f};
307 };
308 
314 {
315 public:
319  DiffDriveMotionModel() = default;
320 
325  bool isHolonomic() const override
326  {
327  return false;
328  }
329 };
330 
336 {
337 public:
341  OmniMotionModel() = default;
342 
347  bool isHolonomic() const override
348  {
349  return true;
350  }
351 };
352 
353 } // namespace mppi
354 
355 #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 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