Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
optimizer.cpp
1 // Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
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 #include "nav2_mppi_controller/optimizer.hpp"
16 
17 #include <limits>
18 #include <memory>
19 #include <stdexcept>
20 #include <string>
21 #include <vector>
22 #include <cmath>
23 #include <xtensor/xmath.hpp>
24 #include <xtensor/xrandom.hpp>
25 #include <xtensor/xnoalias.hpp>
26 
27 #include "nav2_core/controller_exceptions.hpp"
28 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
29 
30 namespace mppi
31 {
32 
33 using namespace xt::placeholders; // NOLINT
34 using xt::evaluation_strategy::immediate;
35 
37  rclcpp_lifecycle::LifecycleNode::WeakPtr parent, const std::string & name,
38  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
39  ParametersHandler * param_handler)
40 {
41  parent_ = parent;
42  name_ = name;
43  costmap_ros_ = costmap_ros;
44  costmap_ = costmap_ros_->getCostmap();
45  parameters_handler_ = param_handler;
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  reset();
56 }
57 
59 {
60  noise_generator_.shutdown();
61 }
62 
64 {
65  std::string motion_model_name;
66 
67  auto & s = settings_;
68  auto getParam = parameters_handler_->getParamGetter(name_);
69  auto getParentParam = parameters_handler_->getParamGetter("");
70  getParam(s.model_dt, "model_dt", 0.05f);
71  getParam(s.time_steps, "time_steps", 56);
72  getParam(s.batch_size, "batch_size", 1000);
73  getParam(s.iteration_count, "iteration_count", 1);
74  getParam(s.temperature, "temperature", 0.3f);
75  getParam(s.gamma, "gamma", 0.015f);
76  getParam(s.base_constraints.vx_max, "vx_max", 0.5f);
77  getParam(s.base_constraints.vx_min, "vx_min", -0.35f);
78  getParam(s.base_constraints.vy, "vy_max", 0.5f);
79  getParam(s.base_constraints.wz, "wz_max", 1.9f);
80  getParam(s.base_constraints.ax_max, "ax_max", 3.0f);
81  getParam(s.base_constraints.ax_min, "ax_min", -3.0f);
82  getParam(s.base_constraints.ay_max, "ay_max", 3.0f);
83  getParam(s.base_constraints.ay_min, "ay_min", -3.0f);
84  getParam(s.base_constraints.az_max, "az_max", 3.5f);
85  getParam(s.sampling_std.vx, "vx_std", 0.2f);
86  getParam(s.sampling_std.vy, "vy_std", 0.2f);
87  getParam(s.sampling_std.wz, "wz_std", 0.4f);
88  getParam(s.retry_attempt_limit, "retry_attempt_limit", 1);
89 
90  s.base_constraints.ax_max = std::abs(s.base_constraints.ax_max);
91  if (s.base_constraints.ax_min > 0.0) {
92  s.base_constraints.ax_min = -1.0 * s.base_constraints.ax_min;
93  RCLCPP_WARN(
94  logger_,
95  "Sign of the parameter ax_min is incorrect, consider setting it negative.");
96  }
97 
98  if (s.base_constraints.ay_min > 0.0) {
99  s.base_constraints.ay_min = -1.0 * s.base_constraints.ay_min;
100  RCLCPP_WARN(
101  logger_,
102  "Sign of the parameter ay_min is incorrect, consider setting it negative.");
103  }
104 
105  getParam(motion_model_name, "motion_model", std::string("DiffDrive"));
106 
107  s.constraints = s.base_constraints;
108  setMotionModel(motion_model_name);
109  parameters_handler_->addPostCallback([this]() {reset();});
110 
111  double controller_frequency;
112  getParentParam(controller_frequency, "controller_frequency", 0.0, ParameterType::Static);
113  setOffset(controller_frequency);
114 }
115 
116 void Optimizer::setOffset(double controller_frequency)
117 {
118  const double controller_period = 1.0 / controller_frequency;
119  constexpr double eps = 1e-6;
120 
121  if ((controller_period + eps) < settings_.model_dt) {
122  RCLCPP_WARN(
123  logger_,
124  "Controller period is less then model dt, consider setting it equal");
125  } else if (abs(controller_period - settings_.model_dt) < eps) {
126  RCLCPP_INFO(
127  logger_,
128  "Controller period is equal to model dt. Control sequence "
129  "shifting is ON");
130  settings_.shift_control_sequence = true;
131  } else {
133  "Controller period more then model dt, set it equal to model dt");
134  }
135 }
136 
137 void Optimizer::reset(bool reset_dynamic_speed_limits)
138 {
139  state_.reset(settings_.batch_size, settings_.time_steps);
140  control_sequence_.reset(settings_.time_steps);
141  control_history_[0] = {0.0f, 0.0f, 0.0f};
142  control_history_[1] = {0.0f, 0.0f, 0.0f};
143  control_history_[2] = {0.0f, 0.0f, 0.0f};
144  control_history_[3] = {0.0f, 0.0f, 0.0f};
145 
146  if (reset_dynamic_speed_limits) {
147  settings_.constraints = settings_.base_constraints;
148  }
149 
150  costs_ = xt::zeros<float>({settings_.batch_size});
151  generated_trajectories_.reset(settings_.batch_size, settings_.time_steps);
152 
153  noise_generator_.reset(settings_, isHolonomic());
154  motion_model_->initialize(settings_.constraints, settings_.model_dt);
155 
156  RCLCPP_INFO(logger_, "Optimizer reset");
157 }
158 
160 {
161  return motion_model_->isHolonomic();
162 }
163 
164 geometry_msgs::msg::TwistStamped Optimizer::evalControl(
165  const geometry_msgs::msg::PoseStamped & robot_pose,
166  const geometry_msgs::msg::Twist & robot_speed,
167  const nav_msgs::msg::Path & plan,
168  const geometry_msgs::msg::Pose & goal,
169  nav2_core::GoalChecker * goal_checker)
170 {
171  prepare(robot_pose, robot_speed, plan, goal, goal_checker);
172 
173  do {
174  optimize();
175  } while (fallback(critics_data_.fail_flag));
176 
177  utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
178  auto control = getControlFromSequenceAsTwist(plan.header.stamp);
179 
180  if (settings_.shift_control_sequence) {
181  shiftControlSequence();
182  }
183 
184  return control;
185 }
186 
188 {
189  for (size_t i = 0; i < settings_.iteration_count; ++i) {
190  generateNoisedTrajectories();
191  critic_manager_.evalTrajectoriesScores(critics_data_);
192  updateControlSequence();
193  }
194 }
195 
196 bool Optimizer::fallback(bool fail)
197 {
198  static size_t counter = 0;
199 
200  if (!fail) {
201  counter = 0;
202  return false;
203  }
204 
205  reset();
206 
207  if (++counter > settings_.retry_attempt_limit) {
208  counter = 0;
209  throw nav2_core::NoValidControl("Optimizer fail to compute path");
210  }
211 
212  return true;
213 }
214 
216  const geometry_msgs::msg::PoseStamped & robot_pose,
217  const geometry_msgs::msg::Twist & robot_speed,
218  const nav_msgs::msg::Path & plan,
219  const geometry_msgs::msg::Pose & goal,
220  nav2_core::GoalChecker * goal_checker)
221 {
222  state_.pose = robot_pose;
223  state_.speed = robot_speed;
224  path_ = utils::toTensor(plan);
225  costs_.fill(0.0f);
226  goal_ = goal;
227 
228  critics_data_.fail_flag = false;
229  critics_data_.goal_checker = goal_checker;
230  critics_data_.motion_model = motion_model_;
231  critics_data_.furthest_reached_path_point.reset();
232  critics_data_.path_pts_valid.reset();
233 }
234 
236 {
237  using namespace xt::placeholders; // NOLINT
238  control_sequence_.vx = xt::roll(control_sequence_.vx, -1);
239  control_sequence_.wz = xt::roll(control_sequence_.wz, -1);
240 
241 
242  xt::view(control_sequence_.vx, -1) =
243  xt::view(control_sequence_.vx, -2);
244 
245  xt::view(control_sequence_.wz, -1) =
246  xt::view(control_sequence_.wz, -2);
247 
248 
249  if (isHolonomic()) {
250  control_sequence_.vy = xt::roll(control_sequence_.vy, -1);
251  xt::view(control_sequence_.vy, -1) =
252  xt::view(control_sequence_.vy, -2);
253  }
254 }
255 
257 {
258  noise_generator_.setNoisedControls(state_, control_sequence_);
259  noise_generator_.generateNextNoises();
260  updateStateVelocities(state_);
261  integrateStateVelocities(generated_trajectories_, state_);
262 }
263 
265 {
266  auto & s = settings_;
267 
268  if (isHolonomic()) {
269  control_sequence_.vy = xt::clip(control_sequence_.vy, -s.constraints.vy, s.constraints.vy);
270  }
271 
272  control_sequence_.vx = xt::clip(control_sequence_.vx, s.constraints.vx_min, s.constraints.vx_max);
273  control_sequence_.wz = xt::clip(control_sequence_.wz, -s.constraints.wz, s.constraints.wz);
274 
275  float max_delta_vx = s.model_dt * s.constraints.ax_max;
276  float min_delta_vx = s.model_dt * s.constraints.ax_min;
277  float max_delta_vy = s.model_dt * s.constraints.ay_max;
278  float min_delta_vy = s.model_dt * s.constraints.ay_min;
279  float max_delta_wz = s.model_dt * s.constraints.az_max;
280  float vx_last = control_sequence_.vx(0);
281  float vy_last = control_sequence_.vy(0);
282  float wz_last = control_sequence_.wz(0);
283  for (unsigned int i = 1; i != control_sequence_.vx.shape(0); i++) {
284  float & vx_curr = control_sequence_.vx(i);
285  if (vx_last > 0) {
286  vx_curr = std::clamp(vx_curr, vx_last + min_delta_vx, vx_last + max_delta_vx);
287  } else {
288  vx_curr = std::clamp(vx_curr, vx_last - max_delta_vx, vx_last - min_delta_vx);
289  }
290  vx_last = vx_curr;
291 
292  float & wz_curr = control_sequence_.wz(i);
293  wz_curr = std::clamp(wz_curr, wz_last - max_delta_wz, wz_last + max_delta_wz);
294  wz_last = wz_curr;
295 
296  if (isHolonomic()) {
297  float & vy_curr = control_sequence_.vy(i);
298  if (vy_last > 0) {
299  vy_curr = std::clamp(vy_curr, vy_last + min_delta_vy, vy_last + max_delta_vy);
300  } else {
301  vy_curr = std::clamp(vy_curr, vy_last - max_delta_vy, vy_last - min_delta_vy);
302  }
303  vy_last = vy_curr;
304  }
305  }
306 
307  motion_model_->applyConstraints(control_sequence_);
308 }
309 
311  models::State & state) const
312 {
313  updateInitialStateVelocities(state);
314  propagateStateVelocitiesFromInitials(state);
315 }
316 
318  models::State & state) const
319 {
320  xt::noalias(xt::view(state.vx, xt::all(), 0)) = static_cast<float>(state.speed.linear.x);
321  xt::noalias(xt::view(state.wz, xt::all(), 0)) = static_cast<float>(state.speed.angular.z);
322 
323  if (isHolonomic()) {
324  xt::noalias(xt::view(state.vy, xt::all(), 0)) = static_cast<float>(state.speed.linear.y);
325  }
326 }
327 
329  models::State & state) const
330 {
331  motion_model_->predict(state);
332 }
333 
335  xt::xtensor<float, 2> & trajectory,
336  const xt::xtensor<float, 2> & sequence) const
337 {
338  float initial_yaw = static_cast<float>(tf2::getYaw(state_.pose.pose.orientation));
339 
340  const auto vx = xt::view(sequence, xt::all(), 0);
341  const auto wz = xt::view(sequence, xt::all(), 1);
342 
343  auto traj_x = xt::view(trajectory, xt::all(), 0);
344  auto traj_y = xt::view(trajectory, xt::all(), 1);
345  auto traj_yaws = xt::view(trajectory, xt::all(), 2);
346 
347  xt::noalias(traj_yaws) = xt::cumsum(wz * settings_.model_dt, 0) + initial_yaw;
348 
349  auto yaw_cos = xt::roll(xt::eval(xt::cos(traj_yaws)), 1);
350  auto yaw_sin = xt::roll(xt::eval(xt::sin(traj_yaws)), 1);
351  xt::view(yaw_cos, 0) = cosf(initial_yaw);
352  xt::view(yaw_sin, 0) = sinf(initial_yaw);
353 
354  auto && dx = xt::eval(vx * yaw_cos);
355  auto && dy = xt::eval(vx * yaw_sin);
356 
357  if (isHolonomic()) {
358  const auto vy = xt::view(sequence, xt::all(), 2);
359  dx = dx - vy * yaw_sin;
360  dy = dy + vy * yaw_cos;
361  }
362 
363  xt::noalias(traj_x) = state_.pose.pose.position.x + xt::cumsum(dx * settings_.model_dt, 0);
364  xt::noalias(traj_y) = state_.pose.pose.position.y + xt::cumsum(dy * settings_.model_dt, 0);
365 }
366 
368  models::Trajectories & trajectories,
369  const models::State & state) const
370 {
371  const float initial_yaw = static_cast<float>(tf2::getYaw(state.pose.pose.orientation));
372 
373  xt::noalias(trajectories.yaws) =
374  xt::cumsum(state.wz * settings_.model_dt, {1}) + initial_yaw;
375 
376  auto yaw_cos = xt::roll(xt::eval(xt::cos(trajectories.yaws)), 1, 1);
377  auto yaw_sin = xt::roll(xt::eval(xt::sin(trajectories.yaws)), 1, 1);
378  xt::view(yaw_cos, xt::all(), 0) = cosf(initial_yaw);
379  xt::view(yaw_sin, xt::all(), 0) = sinf(initial_yaw);
380 
381  auto && dx = xt::eval(state.vx * yaw_cos);
382  auto && dy = xt::eval(state.vx * yaw_sin);
383 
384  if (isHolonomic()) {
385  dx = dx - state.vy * yaw_sin;
386  dy = dy + state.vy * yaw_cos;
387  }
388 
389  xt::noalias(trajectories.x) = state.pose.pose.position.x +
390  xt::cumsum(dx * settings_.model_dt, {1});
391  xt::noalias(trajectories.y) = state.pose.pose.position.y +
392  xt::cumsum(dy * settings_.model_dt, {1});
393 }
394 
395 xt::xtensor<float, 2> Optimizer::getOptimizedTrajectory()
396 {
397  const bool is_holo = isHolonomic();
398  auto && sequence =
399  xt::xtensor<float, 2>::from_shape({settings_.time_steps, is_holo ? 3u : 2u});
400  auto && trajectories = xt::xtensor<float, 2>::from_shape({settings_.time_steps, 3});
401 
402  xt::noalias(xt::view(sequence, xt::all(), 0)) = control_sequence_.vx;
403  xt::noalias(xt::view(sequence, xt::all(), 1)) = control_sequence_.wz;
404 
405  if (is_holo) {
406  xt::noalias(xt::view(sequence, xt::all(), 2)) = control_sequence_.vy;
407  }
408 
409  integrateStateVelocities(trajectories, sequence);
410  return std::move(trajectories);
411 }
412 
414 {
415  const bool is_holo = isHolonomic();
416  auto & s = settings_;
417  auto bounded_noises_vx = state_.cvx - control_sequence_.vx;
418  auto bounded_noises_wz = state_.cwz - control_sequence_.wz;
419  xt::noalias(costs_) +=
420  s.gamma / powf(s.sampling_std.vx, 2) * xt::sum(
421  xt::view(control_sequence_.vx, xt::newaxis(), xt::all()) * bounded_noises_vx, 1, immediate);
422  xt::noalias(costs_) +=
423  s.gamma / powf(s.sampling_std.wz, 2) * xt::sum(
424  xt::view(control_sequence_.wz, xt::newaxis(), xt::all()) * bounded_noises_wz, 1, immediate);
425 
426  if (is_holo) {
427  auto bounded_noises_vy = state_.cvy - control_sequence_.vy;
428  xt::noalias(costs_) +=
429  s.gamma / powf(s.sampling_std.vy, 2) * xt::sum(
430  xt::view(control_sequence_.vy, xt::newaxis(), xt::all()) * bounded_noises_vy,
431  1, immediate);
432  }
433 
434  auto && costs_normalized = costs_ - xt::amin(costs_, immediate);
435  auto && exponents = xt::eval(xt::exp(-1 / settings_.temperature * costs_normalized));
436  auto && softmaxes = xt::eval(exponents / xt::sum(exponents, immediate));
437  auto && softmaxes_extened = xt::eval(xt::view(softmaxes, xt::all(), xt::newaxis()));
438 
439  xt::noalias(control_sequence_.vx) = xt::sum(state_.cvx * softmaxes_extened, 0, immediate);
440  xt::noalias(control_sequence_.wz) = xt::sum(state_.cwz * softmaxes_extened, 0, immediate);
441  if (is_holo) {
442  xt::noalias(control_sequence_.vy) = xt::sum(state_.cvy * softmaxes_extened, 0, immediate);
443  }
444 
445  applyControlSequenceConstraints();
446 }
447 
448 geometry_msgs::msg::TwistStamped Optimizer::getControlFromSequenceAsTwist(
449  const builtin_interfaces::msg::Time & stamp)
450 {
451  unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
452 
453  auto vx = control_sequence_.vx(offset);
454  auto wz = control_sequence_.wz(offset);
455 
456  if (isHolonomic()) {
457  auto vy = control_sequence_.vy(offset);
458  return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
459  }
460 
461  return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
462 }
463 
464 void Optimizer::setMotionModel(const std::string & model)
465 {
466  if (model == "DiffDrive") {
467  motion_model_ = std::make_shared<DiffDriveMotionModel>();
468  } else if (model == "Omni") {
469  motion_model_ = std::make_shared<OmniMotionModel>();
470  } else if (model == "Ackermann") {
471  motion_model_ = std::make_shared<AckermannMotionModel>(parameters_handler_, name_);
472  } else {
474  std::string(
475  "Model " + model + " is not valid! Valid options are DiffDrive, Omni, "
476  "or Ackermann"));
477  }
478  motion_model_->initialize(settings_.constraints, settings_.model_dt);
479 }
480 
481 void Optimizer::setSpeedLimit(double speed_limit, bool percentage)
482 {
483  auto & s = settings_;
484  if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
485  s.constraints.vx_max = s.base_constraints.vx_max;
486  s.constraints.vx_min = s.base_constraints.vx_min;
487  s.constraints.vy = s.base_constraints.vy;
488  s.constraints.wz = s.base_constraints.wz;
489  } else {
490  if (percentage) {
491  // Speed limit is expressed in % from maximum speed of robot
492  double ratio = speed_limit / 100.0;
493  s.constraints.vx_max = s.base_constraints.vx_max * ratio;
494  s.constraints.vx_min = s.base_constraints.vx_min * ratio;
495  s.constraints.vy = s.base_constraints.vy * ratio;
496  s.constraints.wz = s.base_constraints.wz * ratio;
497  } else {
498  // Speed limit is expressed in absolute value
499  double ratio = speed_limit / s.base_constraints.vx_max;
500  s.constraints.vx_max = s.base_constraints.vx_max * ratio;
501  s.constraints.vx_min = s.base_constraints.vx_min * ratio;
502  s.constraints.vy = s.base_constraints.vy * ratio;
503  s.constraints.wz = s.base_constraints.wz * ratio;
504  }
505  }
506 }
507 
509 {
510  return generated_trajectories_;
511 }
512 
513 } // namespace mppi
geometry_msgs::msg::TwistStamped 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:164
void updateStateVelocities(models::State &state) const
Update velocities in state.
Definition: optimizer.cpp:310
void setMotionModel(const std::string &model)
Set the motion model of the vehicle platform.
Definition: optimizer.cpp:464
void setOffset(double controller_frequency)
Using control frequence and time step size, determine if trajectory offset should be used to populate...
Definition: optimizer.cpp:116
void reset(bool reset_dynamic_speed_limits=true)
Reset the optimization problem to initial conditions.
Definition: optimizer.cpp:137
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:215
void integrateStateVelocities(models::Trajectories &trajectories, const models::State &state) const
Rollout velocities in state to poses.
Definition: optimizer.cpp:367
void updateControlSequence()
Update control sequence with state controls weighted by costs using softmax function.
Definition: optimizer.cpp:413
void generateNoisedTrajectories()
updates generated trajectories with noised trajectories from the last cycle's optimal control
Definition: optimizer.cpp:256
bool fallback(bool fail)
Perform fallback behavior to try to recover from a set of trajectories in collision.
Definition: optimizer.cpp:196
bool isHolonomic() const
Whether the motion model is holonomic.
Definition: optimizer.cpp:159
models::Trajectories & getGeneratedTrajectories()
Get the trajectories generated in a cycle for visualization.
Definition: optimizer.cpp:508
void updateInitialStateVelocities(models::State &state) const
Update initial velocity in state.
Definition: optimizer.cpp:317
xt::xtensor< float, 2 > getOptimizedTrajectory()
Get the optimal trajectory for a cycle for visualization.
Definition: optimizer.cpp:395
void shutdown()
Shutdown for optimizer at process end.
Definition: optimizer.cpp:58
void optimize()
Main function to generate, score, and return trajectories.
Definition: optimizer.cpp:187
void setSpeedLimit(double speed_limit, bool percentage)
Set the maximum speed based on the speed limits callback.
Definition: optimizer.cpp:481
void shiftControlSequence()
Shift the optimal control sequence after processing for next iterations initial conditions after exec...
Definition: optimizer.cpp:235
void applyControlSequenceConstraints()
Apply hard vehicle constraints on control sequence.
Definition: optimizer.cpp:264
void getParams()
Obtain the main controller's parameters.
Definition: optimizer.cpp:63
void propagateStateVelocitiesFromInitials(models::State &state) const
predict velocities in state using model for time horizon equal to timesteps
Definition: optimizer.cpp:328
void initialize(rclcpp_lifecycle::LifecycleNode::WeakPtr parent, const std::string &name, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros, ParametersHandler *dynamic_parameters_handler)
Initializes optimizer on startup.
Definition: optimizer.cpp:36
geometry_msgs::msg::TwistStamped getControlFromSequenceAsTwist(const builtin_interfaces::msg::Time &stamp)
Convert control sequence to a twist commant.
Definition: optimizer.cpp:448
Handles getting parameters and dynamic parmaeter changes.
Function-object for checking whether a goal has been reached.
State information: velocities, controls, poses, speed.
Definition: state.hpp:36
Candidate Trajectories.