Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
optimizer.cpp
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 #include "nav2_mppi_controller/optimizer.hpp"
17 
18 #include <limits>
19 #include <memory>
20 #include <stdexcept>
21 #include <string>
22 #include <vector>
23 #include <cmath>
24 #include <chrono>
25 
26 #include "nav2_core/controller_exceptions.hpp"
27 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
28 #include "nav2_ros_common/node_utils.hpp"
29 #include "nav2_ros_common/tf2_factories.hpp"
30 
31 namespace mppi
32 {
33 
35  nav2::LifecycleNode::WeakPtr parent, const std::string & name,
36  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
37  nav2::TransformBuffer::SharedPtr tf_buffer,
38  ParametersHandler * param_handler)
39 {
40  parent_ = parent;
41  name_ = name;
42  costmap_ros_ = costmap_ros;
43  costmap_ = costmap_ros_->getCostmap();
44  parameters_handler_ = param_handler;
45  tf_buffer_ = tf_buffer;
46 
47  auto node = parent_.lock();
48  logger_ = node->get_logger();
49 
50  getParams();
51 
52  critic_manager_.on_configure(parent_, name_, costmap_ros_, parameters_handler_);
53  noise_generator_.initialize(settings_, isHolonomic(), name_, parameters_handler_);
54 
55  // This may throw an exception if not valid and fail initialization
56  nav2::declare_parameter_if_not_declared(
57  node, name_ + ".TrajectoryValidator.plugin",
58  rclcpp::ParameterValue("mppi::DefaultOptimalTrajectoryValidator"));
59  std::string validator_plugin_type = nav2::get_plugin_type_param(
60  node, name_ + ".TrajectoryValidator");
61  validator_loader_ = std::make_unique<pluginlib::ClassLoader<OptimalTrajectoryValidator>>(
62  "nav2_mppi_controller", "mppi::OptimalTrajectoryValidator");
63  trajectory_validator_ = validator_loader_->createUniqueInstance(validator_plugin_type);
64  trajectory_validator_->initialize(
65  parent_, name_ + ".TrajectoryValidator",
66  costmap_ros_, parameters_handler_, tf_buffer, settings_);
67  RCLCPP_INFO(logger_, "Loaded trajectory validator plugin: %s", validator_plugin_type.c_str());
68 
69  reset();
70 }
71 
73 {
74  noise_generator_.shutdown();
75 }
76 
78 {
79  std::string motion_model_name;
80 
81  auto & s = settings_;
82  auto getParam = parameters_handler_->getParamGetter(name_);
83  auto getParentParam = parameters_handler_->getParamGetter("");
84 
85  // Reject dynamic updates to kinematic params when speed limit is active
86  auto kinematic_guard = [this](
87  const rclcpp::Parameter & param,
88  rcl_interfaces::msg::SetParametersResult & result) {
89  if (isSpeedLimitActive()) {
90  result.successful = false;
91  if (!result.reason.empty()) {
92  result.reason += "\n";
93  }
94  result.reason += "Rejected dynamic update to '" + param.get_name() +
95  "': speed limit is active. Clear the speed limit first.";
96  }
97  };
98 
99  const std::vector<std::string> kinematic_params = {
100  "vx_max", "vx_min", "vy_max", "wz_max"};
101  for (const auto & p : kinematic_params) {
102  parameters_handler_->addPreCallback(name_ + "." + p, kinematic_guard);
103  }
104 
105  getParam(s.model_dt, "model_dt", 0.05f);
106  getParam(s.model_delay_vx, "model_delay_vx", 0.0f);
107  getParam(s.model_delay_vy, "model_delay_vy", 0.0f);
108  getParam(s.model_delay_wz, "model_delay_wz", 0.0f);
109  getParam(s.clamp_raw_controls, "clamp_raw_controls", false);
110  getParam(s.time_steps, "time_steps", 56);
111  getParam(s.batch_size, "batch_size", 1000);
112  getParam(s.iteration_count, "iteration_count", 1);
113  getParam(s.temperature, "temperature", 0.3f);
114  getParam(s.gamma, "gamma", 0.015f);
115  getParam(s.base_constraints.vx_max, "vx_max", 0.5f);
116  getParam(s.base_constraints.vx_min, "vx_min", -0.35f);
117  getParam(s.base_constraints.vy, "vy_max", 0.5f);
118  getParam(s.base_constraints.wz, "wz_max", 1.9f);
119  getParam(s.base_constraints.ax_max, "ax_max", 3.0f);
120  getParam(s.base_constraints.ax_min, "ax_min", -3.0f);
121  getParam(s.base_constraints.ay_max, "ay_max", 3.0f);
122  getParam(s.base_constraints.ay_min, "ay_min", -3.0f);
123  getParam(s.base_constraints.az_max, "az_max", 3.5f);
124  getParam(s.sampling_std.vx, "vx_std", 0.2f);
125  getParam(s.sampling_std.vy, "vy_std", 0.2f);
126  getParam(s.sampling_std.wz, "wz_std", 0.4f);
127  getParam(s.retry_attempt_limit, "retry_attempt_limit", 1);
128  getParam(s.open_loop, "open_loop", false);
129  getParam(s.sgf_order, "sgf_order", 2);
130  if (s.sgf_order < 1 || s.sgf_order > 2) {
131  RCLCPP_WARN(logger_, "sgf_order must be 1 or 2, defaulting to 2");
132  s.sgf_order = 2;
133  }
134 
135  s.base_constraints.ax_max = fabs(s.base_constraints.ax_max);
136  if (s.base_constraints.ax_min > 0.0) {
137  s.base_constraints.ax_min = -1.0 * s.base_constraints.ax_min;
138  RCLCPP_WARN(
139  logger_,
140  "Sign of the parameter ax_min is incorrect, consider setting it negative.");
141  }
142 
143  if (s.base_constraints.ay_min > 0.0) {
144  s.base_constraints.ay_min = -1.0 * s.base_constraints.ay_min;
145  RCLCPP_WARN(
146  logger_,
147  "Sign of the parameter ay_min is incorrect, consider setting it negative.");
148  }
149 
150  getParam(motion_model_name, "motion_model", std::string("diff_drive"));
151 
152  s.constraints = s.base_constraints;
153 
154  setMotionModel(motion_model_name);
155  parameters_handler_->addPostCallback([this]() {reset();});
156 
157  double controller_frequency;
158  getParentParam(controller_frequency, "controller_frequency", 0.0, ParameterType::Static);
159  s.controller_period = static_cast<float>(1.0 / controller_frequency);
160  setOffset(s.controller_period);
161 }
162 
163 void Optimizer::setOffset(double controller_period)
164 {
165  constexpr double eps = 1e-6;
166 
167  if ((controller_period + eps) < settings_.model_dt) {
168  RCLCPP_WARN(
169  logger_,
170  "Controller period is less then model dt, consider setting it equal");
171  } else if (abs(controller_period - settings_.model_dt) < eps) {
172  RCLCPP_INFO(
173  logger_,
174  "Controller period is equal to model dt. Control sequence "
175  "shifting is ON");
176  settings_.shift_control_sequence = true;
177  } else {
179  "Controller period more then model dt, set it equal to model dt");
180  }
181 }
182 
183 void Optimizer::reset(bool reset_dynamic_speed_limits)
184 {
185  state_.reset(settings_.batch_size, settings_.time_steps);
186  control_sequence_.reset(settings_.time_steps);
187  control_history_[0] = {0.0f, 0.0f, 0.0f};
188  control_history_[1] = {0.0f, 0.0f, 0.0f};
189  control_history_[2] = {0.0f, 0.0f, 0.0f};
190  control_history_[3] = {0.0f, 0.0f, 0.0f};
191 
192  last_command_vel_ = geometry_msgs::msg::Twist();
193 
194  if (reset_dynamic_speed_limits) {
195  settings_.constraints = settings_.base_constraints;
196  }
197 
198  costs_.setZero(settings_.batch_size);
199  generated_trajectories_.reset(settings_.batch_size, settings_.time_steps);
200 
201  noise_generator_.reset(settings_, isHolonomic());
202  motion_model_->setConstraints(settings_.constraints, settings_.model_dt,
203  settings_.model_delay_vx, settings_.model_delay_vy, settings_.model_delay_wz,
204  settings_.clamp_raw_controls);
205  motion_model_->clearCommandHistory();
206  trajectory_validator_->initialize(
207  parent_, name_ + ".TrajectoryValidator",
208  costmap_ros_, parameters_handler_, tf_buffer_, settings_);
209 
210  RCLCPP_INFO(logger_, "Optimizer reset");
211 }
212 
214 {
215  return motion_model_->isHolonomic();
216 }
217 
219 {
220  // Speed limit is active when current constraints differ from base constraints.
221  // This occurs when setSpeedLimit() has modified the velocity/acceleration limits.
222  const auto & base = settings_.base_constraints;
223  const auto & curr = settings_.constraints;
224  return base.vx_max != curr.vx_max ||
225  base.vx_min != curr.vx_min ||
226  base.vy != curr.vy ||
227  base.wz != curr.wz;
228 }
229 
230 std::tuple<geometry_msgs::msg::TwistStamped, Eigen::ArrayXXf> Optimizer::evalControl(
231  const geometry_msgs::msg::PoseStamped & robot_pose,
232  const geometry_msgs::msg::Twist & robot_speed,
233  const nav_msgs::msg::Path & plan,
234  const geometry_msgs::msg::Pose & goal,
235  nav2_core::GoalChecker * goal_checker)
236 {
237  prepare(robot_pose, robot_speed, plan, goal, goal_checker);
238  Eigen::ArrayXXf optimal_trajectory;
239  bool trajectory_valid = true;
240 
241  do {
242  optimize();
243  optimal_trajectory = getOptimizedTrajectory();
244  switch (trajectory_validator_->validateTrajectory(
245  optimal_trajectory, control_sequence_, robot_pose, robot_speed, plan, goal))
246  {
247  case mppi::ValidationResult::SOFT_RESET:
248  trajectory_valid = false;
249  RCLCPP_WARN(logger_, "Soft reset triggered by trajectory validator");
250  break;
251  case mppi::ValidationResult::FAILURE:
253  "Trajectory validator failed to validate trajectory, hard reset triggered.");
254  case mppi::ValidationResult::SUCCESS:
255  default:
256  trajectory_valid = true;
257  break;
258  }
259  } while (fallback(critics_data_.fail_flag || !trajectory_valid));
260 
261  auto control = getControlFromSequenceAsTwist(plan.header.stamp);
262 
263  last_command_vel_ = control.twist;
264 
265  if (settings_.shift_control_sequence) {
267  }
268 
269  return std::make_tuple(control, optimal_trajectory);
270 }
271 
273 {
274  for (size_t i = 0; i < settings_.iteration_count; ++i) {
276  critic_manager_.evalTrajectoriesScores(critics_data_);
278  }
279 }
280 
281 bool Optimizer::fallback(bool fail)
282 {
283  static size_t counter = 0;
284 
285  if (!fail) {
286  counter = 0;
287  return false;
288  }
289 
290  reset(false /*Don't reset zone-based speed limits after fallback*/);
291 
292  if (++counter > settings_.retry_attempt_limit) {
293  counter = 0;
294  throw nav2_core::NoValidControl("Optimizer fail to compute path");
295  }
296 
297  return true;
298 }
299 
301  const geometry_msgs::msg::PoseStamped & robot_pose,
302  const geometry_msgs::msg::Twist & robot_speed,
303  const nav_msgs::msg::Path & plan,
304  const geometry_msgs::msg::Pose & goal,
305  nav2_core::GoalChecker * goal_checker)
306 {
307  if (settings_.open_loop) {
308  state_.speed = last_command_vel_;
309  } else {
310  // Predict state one controller_period forward toward the last command to compensate
311  // for the latency between measurement and when this command will take effect.
312  // Clamp to physically achievable range so prediction never exceeds dynamics.
313  const auto & c = settings_.constraints;
314  const double dt = settings_.controller_period;
315  state_.speed = robot_speed;
316  state_.speed.linear.x = std::clamp(
317  last_command_vel_.linear.x,
318  robot_speed.linear.x + dt * c.ax_min,
319  robot_speed.linear.x + dt * c.ax_max);
320  state_.speed.angular.z = std::clamp(
321  last_command_vel_.angular.z,
322  robot_speed.angular.z - dt * c.az_max,
323  robot_speed.angular.z + dt * c.az_max);
324  if (isHolonomic()) {
325  state_.speed.linear.y = std::clamp(
326  last_command_vel_.linear.y,
327  robot_speed.linear.y + dt * c.ay_min,
328  robot_speed.linear.y + dt * c.ay_max);
329  }
330  }
331 
332  state_.pose = robot_pose;
333  state_.local_path_length = nav2_util::geometry_utils::calculate_path_length(plan);
334  path_ = utils::toTensor(plan);
335  costs_.setZero(settings_.batch_size);
336  goal_ = goal;
337 
338  critics_data_.fail_flag = false;
339  critics_data_.goal_checker = goal_checker;
340  critics_data_.motion_model = motion_model_;
341  critics_data_.furthest_reached_path_point.reset();
342  critics_data_.path_pts_valid.reset();
343 }
344 
346 {
347  auto size = control_sequence_.vx.size();
348  utils::shiftColumnsByOnePlace(control_sequence_.vx, -1);
349  utils::shiftColumnsByOnePlace(control_sequence_.wz, -1);
350  control_sequence_.vx(size - 1) = control_sequence_.vx(size - 2);
351  control_sequence_.wz(size - 1) = control_sequence_.wz(size - 2);
352 
353  if (isHolonomic()) {
354  utils::shiftColumnsByOnePlace(control_sequence_.vy, -1);
355  control_sequence_.vy(size - 1) = control_sequence_.vy(size - 2);
356  }
357 }
358 
360 {
362  noise_generator_.setNoisedControls(state_, control_sequence_);
363  noise_generator_.generateNextNoises();
364  updateStateVelocities(state_);
365  integrateStateVelocities(generated_trajectories_, state_);
366 }
367 
369 {
370  // Enforce t=0 to be dynamically feasible from the current speed for inter-iteration feasibility
371  // Re-centers the distribution at t=0, but still applied in a information theoretic sound way
372  auto & s = settings_;
373  float first_dt = s.controller_period;
374  float max_delta_vx = first_dt * s.constraints.ax_max;
375  float min_delta_vx = first_dt * s.constraints.ax_min;
376  float max_delta_vy = first_dt * s.constraints.ay_max;
377  float min_delta_vy = first_dt * s.constraints.ay_min;
378  float max_delta_wz = first_dt * s.constraints.az_max;
379 
380  float speed_vx = static_cast<float>(state_.speed.linear.x);
381  float speed_wz = static_cast<float>(state_.speed.angular.z);
382  if (s.shift_control_sequence) {
383  // When shifting, vx(0) is not sent and represents 'now'
384  // so that vx(1), the sent command, needs to be only one step away.
385  control_sequence_.vx(0) = speed_vx;
386  control_sequence_.wz(0) = speed_wz;
387  if (isHolonomic()) {
388  control_sequence_.vy(0) = static_cast<float>(state_.speed.linear.y);
389  }
390  } else {
391  // When not shifting, vx(0) is the sent command, clamp to the feasible envelope
392  control_sequence_.vx(0) = utils::clampVelocityByAccel(
393  speed_vx, control_sequence_.vx(0), min_delta_vx, max_delta_vx);
394  control_sequence_.wz(0) = utils::clampVelocityByAccel(
395  speed_wz, control_sequence_.wz(0), -max_delta_wz, max_delta_wz);
396  if (isHolonomic()) {
397  float speed_vy = static_cast<float>(state_.speed.linear.y);
398  control_sequence_.vy(0) = utils::clampVelocityByAccel(
399  speed_vy, control_sequence_.vy(0), min_delta_vy, max_delta_vy);
400  }
401  }
402 }
403 
405 {
406  auto & s = settings_;
407 
408  // Apply constraints to set the optimal control sequence within bounds
409  motion_model_->applyConstraints(control_sequence_);
410 
411  // Use controller_period for t=0 to realistically model physical limits
412  float first_dt = s.controller_period;
413  float max_delta_vx = first_dt * s.constraints.ax_max;
414  float min_delta_vx = first_dt * s.constraints.ax_min;
415  float max_delta_vy = first_dt * s.constraints.ay_max;
416  float min_delta_vy = first_dt * s.constraints.ay_min;
417  float max_delta_wz = first_dt * s.constraints.az_max;
418 
419  // Initialize as the current speed to create inter-iteration dynamic feasibility
420  float vx_last = static_cast<float>(state_.speed.linear.x);
421  float wz_last = static_cast<float>(state_.speed.angular.z);
422  float vy_last = isHolonomic() ? static_cast<float>(state_.speed.linear.y) : 0.0f;
423 
424  // When shifting, vx(0) is "now" and not sent. Pin it so vx(1), the sent command,
425  // is exactly one constraint step from current speed when shift_control_sequence
426  if (s.shift_control_sequence) {
427  control_sequence_.vx(0) = vx_last;
428  control_sequence_.wz(0) = wz_last;
429  if (isHolonomic()) {
430  control_sequence_.vy(0) = vy_last;
431  }
432  }
433 
434  for (unsigned int i = 0; i != control_sequence_.vx.size(); i++) {
435  // After first timestep, switch to MPC model_dt for intra-iteration feasibility
436  if (i == 1) {
437  max_delta_vx = s.model_dt * s.constraints.ax_max;
438  min_delta_vx = s.model_dt * s.constraints.ax_min;
439  max_delta_vy = s.model_dt * s.constraints.ay_max;
440  min_delta_vy = s.model_dt * s.constraints.ay_min;
441  max_delta_wz = s.model_dt * s.constraints.az_max;
442  }
443 
444  float & vx_curr = control_sequence_.vx(i);
445  vx_curr = utils::clamp(s.constraints.vx_min, s.constraints.vx_max, vx_curr);
446  vx_curr = utils::clampVelocityByAccel(vx_last, vx_curr, min_delta_vx, max_delta_vx);
447  vx_last = vx_curr;
448 
449  float & wz_curr = control_sequence_.wz(i);
450  wz_curr = utils::clamp(-s.constraints.wz, s.constraints.wz, wz_curr);
451  wz_curr = utils::clampVelocityByAccel(wz_last, wz_curr, -max_delta_wz, max_delta_wz);
452  wz_last = wz_curr;
453 
454  if (isHolonomic()) {
455  float & vy_curr = control_sequence_.vy(i);
456  vy_curr = utils::clamp(-s.constraints.vy, s.constraints.vy, vy_curr);
457  vy_curr = utils::clampVelocityByAccel(vy_last, vy_curr, min_delta_vy, max_delta_vy);
458  vy_last = vy_curr;
459  }
460  }
461 
462  // Apply again to ensure accel constraints don't violate specialty limits
463  motion_model_->applyConstraints(control_sequence_);
464 }
465 
467  models::State & state) const
468 {
471 }
472 
474 {
475  state.vx.col(0) = static_cast<float>(state.speed.linear.x);
476  state.wz.col(0) = static_cast<float>(state.speed.angular.z);
477 
478  if (isHolonomic()) {
479  state.vy.col(0) = static_cast<float>(state.speed.linear.y);
480  }
481 }
482 
484  models::State & state) const
485 {
486  motion_model_->predict(state);
487 }
488 
490  Eigen::Array<float, Eigen::Dynamic, 3> & trajectory,
491  const Eigen::ArrayXXf & sequence) const
492 {
493  float initial_yaw = static_cast<float>(tf2::getYaw(state_.pose.pose.orientation));
494 
495  const auto vx = sequence.col(0);
496  const auto wz = sequence.col(1);
497 
498  auto traj_x = trajectory.col(0);
499  auto traj_y = trajectory.col(1);
500  auto traj_yaws = trajectory.col(2);
501 
502  const size_t n_size = traj_yaws.size();
503  if (n_size == 0) {
504  return;
505  }
506 
507  float last_yaw = initial_yaw;
508  for (size_t i = 0; i != n_size; i++) {
509  last_yaw += wz(i) * settings_.model_dt;
510  traj_yaws(i) = last_yaw;
511  }
512 
513  Eigen::ArrayXf yaw_cos = traj_yaws.cos();
514  Eigen::ArrayXf yaw_sin = traj_yaws.sin();
515  utils::shiftColumnsByOnePlace(yaw_cos, 1);
516  utils::shiftColumnsByOnePlace(yaw_sin, 1);
517  yaw_cos(0) = cosf(initial_yaw);
518  yaw_sin(0) = sinf(initial_yaw);
519 
520  auto dx = (vx * yaw_cos).eval();
521  auto dy = (vx * yaw_sin).eval();
522 
523  if (isHolonomic()) {
524  auto vy = sequence.col(2);
525  dx = (dx - vy * yaw_sin).eval();
526  dy = (dy + vy * yaw_cos).eval();
527  }
528 
529  float last_x = state_.pose.pose.position.x;
530  float last_y = state_.pose.pose.position.y;
531  for (size_t i = 0; i != n_size; i++) {
532  last_x += dx(i) * settings_.model_dt;
533  last_y += dy(i) * settings_.model_dt;
534  traj_x(i) = last_x;
535  traj_y(i) = last_y;
536  }
537 }
538 
540  models::Trajectories & trajectories,
541  const models::State & state) const
542 {
543  auto initial_yaw = static_cast<float>(tf2::getYaw(state.pose.pose.orientation));
544  const size_t n_cols = trajectories.yaws.cols();
545 
546  Eigen::ArrayXf last_yaws = Eigen::ArrayXf::Constant(trajectories.yaws.rows(), initial_yaw);
547  for (size_t i = 0; i != n_cols; i++) {
548  last_yaws += state.wz.col(i) * settings_.model_dt;
549  trajectories.yaws.col(i) = last_yaws;
550  }
551 
552  Eigen::ArrayXXf yaw_cos = trajectories.yaws.cos();
553  Eigen::ArrayXXf yaw_sin = trajectories.yaws.sin();
554  utils::shiftColumnsByOnePlace(yaw_cos, 1);
555  utils::shiftColumnsByOnePlace(yaw_sin, 1);
556  yaw_cos.col(0) = cosf(initial_yaw);
557  yaw_sin.col(0) = sinf(initial_yaw);
558 
559  auto dx = (state.vx * yaw_cos).eval();
560  auto dy = (state.vx * yaw_sin).eval();
561 
562  if (isHolonomic()) {
563  dx -= state.vy * yaw_sin;
564  dy += state.vy * yaw_cos;
565  }
566 
567  Eigen::ArrayXf last_x = Eigen::ArrayXf::Constant(
568  trajectories.x.rows(),
569  state.pose.pose.position.x);
570  Eigen::ArrayXf last_y = Eigen::ArrayXf::Constant(
571  trajectories.y.rows(),
572  state.pose.pose.position.y);
573 
574  for (size_t i = 0; i != n_cols; i++) {
575  last_x += dx.col(i) * settings_.model_dt;
576  last_y += dy.col(i) * settings_.model_dt;
577  trajectories.x.col(i) = last_x;
578  trajectories.y.col(i) = last_y;
579  }
580 }
581 
583 {
584  const bool is_holo = isHolonomic();
585  Eigen::ArrayXXf sequence = Eigen::ArrayXXf(settings_.time_steps, is_holo ? 3 : 2);
586  Eigen::Array<float, Eigen::Dynamic, 3> trajectories =
587  Eigen::Array<float, Eigen::Dynamic, 3>(settings_.time_steps, 3);
588 
589  sequence.col(0) = control_sequence_.vx;
590  sequence.col(1) = control_sequence_.wz;
591 
592  if (is_holo) {
593  sequence.col(2) = control_sequence_.vy;
594  }
595 
596  integrateStateVelocities(trajectories, sequence);
597  return trajectories;
598 }
599 
601 {
602  return control_sequence_;
603 }
604 
606 {
607  const bool is_holo = isHolonomic();
608  auto & s = settings_;
609 
610  auto vx_T = control_sequence_.vx.transpose();
611  auto bounded_noises_vx = state_.cvx.rowwise() - vx_T;
612  const float gamma_vx = s.gamma / (s.sampling_std.vx * s.sampling_std.vx);
613  costs_ += (gamma_vx * (bounded_noises_vx.rowwise() * vx_T).rowwise().sum()).eval();
614 
615  if (s.sampling_std.wz > 0.0f) {
616  auto wz_T = control_sequence_.wz.transpose();
617  auto bounded_noises_wz = state_.cwz.rowwise() - wz_T;
618  const float gamma_wz = s.gamma / (s.sampling_std.wz * s.sampling_std.wz);
619  costs_ += (gamma_wz * (bounded_noises_wz.rowwise() * wz_T).rowwise().sum()).eval();
620  }
621 
622  if (is_holo) {
623  auto vy_T = control_sequence_.vy.transpose();
624  auto bounded_noises_vy = state_.cvy.rowwise() - vy_T;
625  const float gamma_vy = s.gamma / (s.sampling_std.vy * s.sampling_std.vy);
626  costs_ += (gamma_vy * (bounded_noises_vy.rowwise() * vy_T).rowwise().sum()).eval();
627  }
628 
629  auto costs_normalized = costs_ - costs_.minCoeff();
630  const float inv_temp = 1.0f / s.temperature;
631  auto softmaxes = (-inv_temp * costs_normalized).exp().eval();
632  softmaxes /= softmaxes.sum();
633 
634  auto softmax_mat = softmaxes.matrix();
635  control_sequence_.vx = state_.cvx.transpose().matrix() * softmax_mat;
636  control_sequence_.wz = state_.cwz.transpose().matrix() * softmax_mat;
637 
638  if (is_holo) {
639  control_sequence_.vy = state_.cvy.transpose().matrix() * softmax_mat;
640  }
641 
642  utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
643 
645 }
646 
647 geometry_msgs::msg::TwistStamped Optimizer::getControlFromSequenceAsTwist(
648  const builtin_interfaces::msg::Time & stamp)
649 {
650  unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
651 
652  auto vx = control_sequence_.vx(offset);
653  auto wz = control_sequence_.wz(offset);
654  auto vy = isHolonomic() ? control_sequence_.vy(offset) : 0.0f;
655 
656  // Update the command history for the motion model's latency compensation mechanism
657  motion_model_->pushCommandHistory(vx, vy, wz);
658 
659  if (isHolonomic()) {
660  return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
661  }
662 
663  return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
664 }
665 
666 void Optimizer::setMotionModel(const std::string & motion_model_name)
667 {
668  auto node = parent_.lock();
669  const std::string plugin_ns = name_ + "." + motion_model_name;
670  std::string plugin_type;
671  motion_model_loader_ =
672  std::make_unique<pluginlib::ClassLoader<MotionModel>>(
673  "nav2_mppi_controller", "mppi::MotionModel");
674 
675  try {
676  plugin_type = nav2::get_plugin_type_param(node, plugin_ns);
677  motion_model_ = motion_model_loader_->createSharedInstance(plugin_type);
678  motion_model_->initialize(parameters_handler_, plugin_ns);
679  motion_model_->setConstraints(settings_.constraints, settings_.model_dt,
680  settings_.model_delay_vx, settings_.model_delay_vy, settings_.model_delay_wz,
681  settings_.clamp_raw_controls);
682  } catch (const pluginlib::PluginlibException & ex) {
684  std::string("Failed to load motion model plugin '") + motion_model_name +
685  "': " + ex.what());
686  }
687 
688  RCLCPP_INFO(logger_, "Loaded motion model plugin: %s", plugin_type.c_str());
689 }
690 
691 void Optimizer::setSpeedLimit(double speed_limit, bool percentage)
692 {
693  auto & s = settings_;
694  if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
695  s.constraints.vx_max = s.base_constraints.vx_max;
696  s.constraints.vx_min = s.base_constraints.vx_min;
697  s.constraints.vy = s.base_constraints.vy;
698  s.constraints.wz = s.base_constraints.wz;
699  } else {
700  if (percentage) {
701  // Speed limit is expressed in % from maximum speed of robot
702  double ratio = speed_limit / 100.0;
703  s.constraints.vx_max = s.base_constraints.vx_max * ratio;
704  s.constraints.vx_min = s.base_constraints.vx_min * ratio;
705  s.constraints.vy = s.base_constraints.vy * ratio;
706  s.constraints.wz = s.base_constraints.wz * ratio;
707  } else {
708  // Speed limit is expressed in absolute value
709  double ratio = speed_limit / s.base_constraints.vx_max;
710  s.constraints.vx_max = s.base_constraints.vx_max * ratio;
711  s.constraints.vx_min = s.base_constraints.vx_min * ratio;
712  s.constraints.vy = s.base_constraints.vy * ratio;
713  s.constraints.wz = s.base_constraints.wz * ratio;
714  }
715  }
716  motion_model_->setConstraints(settings_.constraints, settings_.model_dt,
717  settings_.model_delay_vx, settings_.model_delay_vy, settings_.model_delay_wz,
718  settings_.clamp_raw_controls);
719 }
720 
722 {
723  return generated_trajectories_;
724 }
725 
726 } // namespace mppi
void on_configure(nav2::LifecycleNode::WeakPtr parent, const std::string &name, std::shared_ptr< nav2_costmap_2d::Costmap2DROS >, ParametersHandler *)
Configure critic manager on bringup and load plugins.
void evalTrajectoriesScores(CriticData &data)
Score trajectories by the set of loaded critic functions.
void reset(mppi::models::OptimizerSettings &settings, bool is_holonomic)
Reset noise generator with settings and model types.
void initialize(mppi::models::OptimizerSettings &settings, bool is_holonomic, const std::string &name, ParametersHandler *param_handler)
Initialize noise generator with settings and model types.
void setNoisedControls(models::State &state, const models::ControlSequence &control_sequence)
set noised control_sequence to state controls
void shutdown()
Shutdown noise generator thread.
void generateNextNoises()
Signal to the noise thread the controller is ready to generate a new noised control for the next iter...
void updateStateVelocities(models::State &state) const
Update velocities in state.
Definition: optimizer.cpp:466
const models::ControlSequence & getOptimalControlSequence()
Get the optimal control sequence for a cycle for visualization.
Definition: optimizer.cpp:600
void setMotionModel(const std::string &model)
Set the motion model of the vehicle platform.
Definition: optimizer.cpp:666
Eigen::ArrayXXf getOptimizedTrajectory()
Get the optimal trajectory for a cycle for visualization.
Definition: optimizer.cpp:582
void reset(bool reset_dynamic_speed_limits=true)
Reset the optimization problem to initial conditions.
Definition: optimizer.cpp:183
rclcpp::Logger logger_
Caution, keep references.
Definition: optimizer.hpp:330
void prepare(const geometry_msgs::msg::PoseStamped &robot_pose, const geometry_msgs::msg::Twist &robot_speed, const nav_msgs::msg::Path &plan, const geometry_msgs::msg::Pose &goal, nav2_core::GoalChecker *goal_checker)
Prepare state information on new request for trajectory rollouts.
Definition: optimizer.cpp:300
std::tuple< geometry_msgs::msg::TwistStamped, Eigen::ArrayXXf > evalControl(const geometry_msgs::msg::PoseStamped &robot_pose, const geometry_msgs::msg::Twist &robot_speed, const nav_msgs::msg::Path &plan, const geometry_msgs::msg::Pose &goal, nav2_core::GoalChecker *goal_checker)
Compute control using MPPI algorithm.
Definition: optimizer.cpp:230
void integrateStateVelocities(models::Trajectories &trajectories, const models::State &state) const
Rollout velocities in state to poses.
Definition: optimizer.cpp:539
void applyControlSequenceInterIterationConstraints()
Apply inter-iteration dynamic feasibility constraints on the first control sequence element before no...
Definition: optimizer.cpp:368
void updateControlSequence()
Update control sequence with state controls weighted by costs using softmax function.
Definition: optimizer.cpp:605
bool isSpeedLimitActive() const
Check if a dynamic speed limit is currently active.
Definition: optimizer.cpp:218
void generateNoisedTrajectories()
updates generated trajectories with noised trajectories from the last cycle's optimal control
Definition: optimizer.cpp:359
bool fallback(bool fail)
Perform fallback behavior to try to recover from a set of trajectories in collision.
Definition: optimizer.cpp:281
bool isHolonomic() const
Whether the motion model is holonomic.
Definition: optimizer.cpp:213
models::Trajectories & getGeneratedTrajectories()
Get the trajectories generated in a cycle for visualization.
Definition: optimizer.cpp:721
void updateInitialStateVelocities(models::State &state) const
Update initial velocity in state.
Definition: optimizer.cpp:473
void shutdown()
Shutdown for optimizer at process end.
Definition: optimizer.cpp:72
void optimize()
Main function to generate, score, and return trajectories.
Definition: optimizer.cpp:272
void setOffset(double controller_period)
Using control period and time step size, determine if trajectory offset should be used to populate in...
Definition: optimizer.cpp:163
void setSpeedLimit(double speed_limit, bool percentage)
Set the maximum speed based on the speed limits callback.
Definition: optimizer.cpp:691
void shiftControlSequence()
Shift the optimal control sequence after processing for next iterations initial conditions after exec...
Definition: optimizer.cpp:345
void applyControlSequenceConstraints()
Apply hard vehicle constraints on control sequence.
Definition: optimizer.cpp:404
void getParams()
Obtain the main controller's parameters.
Definition: optimizer.cpp:77
void propagateStateVelocitiesFromInitials(models::State &state) const
predict velocities in state using model for time horizon equal to timesteps
Definition: optimizer.cpp:483
geometry_msgs::msg::TwistStamped getControlFromSequenceAsTwist(const builtin_interfaces::msg::Time &stamp)
Convert control sequence to a twist command.
Definition: optimizer.cpp:647
void initialize(nav2::LifecycleNode::WeakPtr parent, const std::string &name, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros, nav2::TransformBuffer::SharedPtr tf_buffer, ParametersHandler *dynamic_parameters_handler)
Initializes optimizer on startup.
Definition: optimizer.cpp:34
Handles getting parameters and dynamic parameter changes.
void addPostCallback(T &&callback)
Set a callback to process after parameter changes.
void addPreCallback(const std::string &name, T &&callback)
Set a callback to process before parameter changes.
auto getParamGetter(const std::string &ns)
Get an object to retrieve parameters.
Function-object for checking whether a goal has been reached.
A control sequence over time (e.g. trajectory)
State information: velocities, controls, poses, speed.
Definition: state.hpp:32
void reset(unsigned int batch_size, unsigned int time_steps)
Reset state data.
Definition: state.hpp:48
Candidate Trajectories.
void reset(unsigned int batch_size, unsigned int time_steps)
Reset state data.