16 #include "nav2_mppi_controller/optimizer.hpp"
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"
34 nav2::LifecycleNode::WeakPtr parent,
const std::string & name,
35 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
36 std::shared_ptr<tf2_ros::Buffer> tf_buffer,
41 costmap_ros_ = costmap_ros;
42 costmap_ = costmap_ros_->getCostmap();
43 parameters_handler_ = param_handler;
44 tf_buffer_ = tf_buffer;
46 auto node = parent_.lock();
51 critic_manager_.
on_configure(parent_, name_, costmap_ros_, parameters_handler_);
55 nav2::declare_parameter_if_not_declared(
56 node, name_ +
".TrajectoryValidator.plugin",
57 rclcpp::ParameterValue(
"mppi::DefaultOptimalTrajectoryValidator"));
58 std::string validator_plugin_type = nav2::get_plugin_type_param(
59 node, name_ +
".TrajectoryValidator");
60 validator_loader_ = std::make_unique<pluginlib::ClassLoader<OptimalTrajectoryValidator>>(
61 "nav2_mppi_controller",
"mppi::OptimalTrajectoryValidator");
62 trajectory_validator_ = validator_loader_->createUniqueInstance(validator_plugin_type);
63 trajectory_validator_->initialize(
64 parent_, name_ +
".TrajectoryValidator",
65 costmap_ros_, parameters_handler_, tf_buffer, settings_);
66 RCLCPP_INFO(
logger_,
"Loaded trajectory validator plugin: %s", validator_plugin_type.c_str());
78 std::string motion_model_name;
83 getParam(s.model_dt,
"model_dt", 0.05f);
84 getParam(s.time_steps,
"time_steps", 56);
85 getParam(s.batch_size,
"batch_size", 1000);
86 getParam(s.iteration_count,
"iteration_count", 1);
87 getParam(s.temperature,
"temperature", 0.3f);
88 getParam(s.gamma,
"gamma", 0.015f);
89 getParam(s.base_constraints.vx_max,
"vx_max", 0.5f);
90 getParam(s.base_constraints.vx_min,
"vx_min", -0.35f);
91 getParam(s.base_constraints.vy,
"vy_max", 0.5f);
92 getParam(s.base_constraints.wz,
"wz_max", 1.9f);
93 getParam(s.base_constraints.ax_max,
"ax_max", 3.0f);
94 getParam(s.base_constraints.ax_min,
"ax_min", -3.0f);
95 getParam(s.base_constraints.ay_max,
"ay_max", 3.0f);
96 getParam(s.base_constraints.ay_min,
"ay_min", -3.0f);
97 getParam(s.base_constraints.az_max,
"az_max", 3.5f);
98 getParam(s.sampling_std.vx,
"vx_std", 0.2f);
99 getParam(s.sampling_std.vy,
"vy_std", 0.2f);
100 getParam(s.sampling_std.wz,
"wz_std", 0.4f);
101 getParam(s.retry_attempt_limit,
"retry_attempt_limit", 1);
102 getParam(s.open_loop,
"open_loop",
false);
104 s.base_constraints.ax_max = fabs(s.base_constraints.ax_max);
105 if (s.base_constraints.ax_min > 0.0) {
106 s.base_constraints.ax_min = -1.0 * s.base_constraints.ax_min;
109 "Sign of the parameter ax_min is incorrect, consider setting it negative.");
112 if (s.base_constraints.ay_min > 0.0) {
113 s.base_constraints.ay_min = -1.0 * s.base_constraints.ay_min;
116 "Sign of the parameter ay_min is incorrect, consider setting it negative.");
120 getParam(motion_model_name,
"motion_model", std::string(
"DiffDrive"));
122 s.constraints = s.base_constraints;
127 double controller_frequency;
128 getParentParam(controller_frequency,
"controller_frequency", 0.0, ParameterType::Static);
134 const double controller_period = 1.0 / controller_frequency;
135 constexpr
double eps = 1e-6;
137 if ((controller_period + eps) < settings_.model_dt) {
140 "Controller period is less then model dt, consider setting it equal");
141 }
else if (abs(controller_period - settings_.model_dt) < eps) {
144 "Controller period is equal to model dt. Control sequence "
146 settings_.shift_control_sequence =
true;
149 "Controller period more then model dt, set it equal to model dt");
155 state_.
reset(settings_.batch_size, settings_.time_steps);
156 control_sequence_.reset(settings_.time_steps);
157 control_history_[0] = {0.0f, 0.0f, 0.0f};
158 control_history_[1] = {0.0f, 0.0f, 0.0f};
159 control_history_[2] = {0.0f, 0.0f, 0.0f};
160 control_history_[3] = {0.0f, 0.0f, 0.0f};
162 if (settings_.open_loop) {
163 last_command_vel_ = geometry_msgs::msg::Twist();
166 if (reset_dynamic_speed_limits) {
167 settings_.constraints = settings_.base_constraints;
170 costs_.setZero(settings_.batch_size);
171 generated_trajectories_.
reset(settings_.batch_size, settings_.time_steps);
174 motion_model_->initialize(settings_.constraints, settings_.model_dt);
175 trajectory_validator_->initialize(
176 parent_, name_ +
".TrajectoryValidator",
177 costmap_ros_, parameters_handler_, tf_buffer_, settings_);
179 RCLCPP_INFO(
logger_,
"Optimizer reset");
184 return motion_model_->isHolonomic();
188 const geometry_msgs::msg::PoseStamped & robot_pose,
189 const geometry_msgs::msg::Twist & robot_speed,
190 const nav_msgs::msg::Path & plan,
191 const geometry_msgs::msg::Pose & goal,
194 prepare(robot_pose, robot_speed, plan, goal, goal_checker);
195 Eigen::ArrayXXf optimal_trajectory;
196 bool trajectory_valid =
true;
201 switch (trajectory_validator_->validateTrajectory(
202 optimal_trajectory, control_sequence_, robot_pose, robot_speed, plan, goal))
204 case mppi::ValidationResult::SOFT_RESET:
205 trajectory_valid =
false;
206 RCLCPP_WARN(
logger_,
"Soft reset triggered by trajectory validator");
208 case mppi::ValidationResult::FAILURE:
210 "Trajectory validator failed to validate trajectory, hard reset triggered.");
211 case mppi::ValidationResult::SUCCESS:
213 trajectory_valid =
true;
216 }
while (
fallback(critics_data_.fail_flag || !trajectory_valid));
218 utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
221 last_command_vel_ = control.twist;
223 if (settings_.shift_control_sequence) {
227 return std::make_tuple(control, optimal_trajectory);
232 for (
size_t i = 0; i < settings_.iteration_count; ++i) {
241 static size_t counter = 0;
250 if (++counter > settings_.retry_attempt_limit) {
259 const geometry_msgs::msg::PoseStamped & robot_pose,
260 const geometry_msgs::msg::Twist & robot_speed,
261 const nav_msgs::msg::Path & plan,
262 const geometry_msgs::msg::Pose & goal,
265 state_.pose = robot_pose;
266 state_.speed = settings_.open_loop ? last_command_vel_ : robot_speed;
267 state_.local_path_length = nav2_util::geometry_utils::calculate_path_length(plan);
268 path_ = utils::toTensor(plan);
272 critics_data_.fail_flag =
false;
273 critics_data_.goal_checker = goal_checker;
274 critics_data_.motion_model = motion_model_;
275 critics_data_.furthest_reached_path_point.reset();
276 critics_data_.path_pts_valid.reset();
281 auto size = control_sequence_.vx.size();
282 utils::shiftColumnsByOnePlace(control_sequence_.vx, -1);
283 utils::shiftColumnsByOnePlace(control_sequence_.wz, -1);
284 control_sequence_.vx(size - 1) = control_sequence_.vx(size - 2);
285 control_sequence_.wz(size - 1) = control_sequence_.wz(size - 2);
288 utils::shiftColumnsByOnePlace(control_sequence_.vy, -1);
289 control_sequence_.vy(size - 1) = control_sequence_.vy(size - 2);
303 auto & s = settings_;
305 float max_delta_vx = s.model_dt * s.constraints.ax_max;
306 float min_delta_vx = s.model_dt * s.constraints.ax_min;
307 float max_delta_vy = s.model_dt * s.constraints.ay_max;
308 float min_delta_vy = s.model_dt * s.constraints.ay_min;
309 float max_delta_wz = s.model_dt * s.constraints.az_max;
310 float vx_last = utils::clamp(s.constraints.vx_min, s.constraints.vx_max, control_sequence_.vx(0));
311 float wz_last = utils::clamp(-s.constraints.wz, s.constraints.wz, control_sequence_.wz(0));
312 control_sequence_.vx(0) = vx_last;
313 control_sequence_.wz(0) = wz_last;
316 vy_last = utils::clamp(-s.constraints.vy, s.constraints.vy, control_sequence_.vy(0));
317 control_sequence_.vy(0) = vy_last;
320 for (
unsigned int i = 1; i != control_sequence_.vx.size(); i++) {
321 float & vx_curr = control_sequence_.vx(i);
322 vx_curr = utils::clamp(s.constraints.vx_min, s.constraints.vx_max, vx_curr);
324 vx_curr = utils::clamp(vx_last + min_delta_vx, vx_last + max_delta_vx, vx_curr);
326 vx_curr = utils::clamp(vx_last - max_delta_vx, vx_last - min_delta_vx, vx_curr);
330 float & wz_curr = control_sequence_.wz(i);
331 wz_curr = utils::clamp(-s.constraints.wz, s.constraints.wz, wz_curr);
332 wz_curr = utils::clamp(wz_last - max_delta_wz, wz_last + max_delta_wz, wz_curr);
336 float & vy_curr = control_sequence_.vy(i);
337 vy_curr = utils::clamp(-s.constraints.vy, s.constraints.vy, vy_curr);
339 vy_curr = utils::clamp(vy_last + min_delta_vy, vy_last + max_delta_vy, vy_curr);
341 vy_curr = utils::clamp(vy_last - max_delta_vy, vy_last - min_delta_vy, vy_curr);
347 motion_model_->applyConstraints(control_sequence_);
359 state.vx.col(0) =
static_cast<float>(state.speed.linear.x);
360 state.wz.col(0) =
static_cast<float>(state.speed.angular.z);
363 state.vy.col(0) =
static_cast<float>(state.speed.linear.y);
370 motion_model_->predict(state);
374 Eigen::Array<float, Eigen::Dynamic, 3> & trajectory,
375 const Eigen::ArrayXXf & sequence)
const
377 float initial_yaw =
static_cast<float>(tf2::getYaw(state_.pose.pose.orientation));
379 const auto vx = sequence.col(0);
380 const auto wz = sequence.col(1);
382 auto traj_x = trajectory.col(0);
383 auto traj_y = trajectory.col(1);
384 auto traj_yaws = trajectory.col(2);
386 const size_t n_size = traj_yaws.size();
391 float last_yaw = initial_yaw;
392 for(
size_t i = 0; i != n_size; i++) {
393 last_yaw += wz(i) * settings_.model_dt;
394 traj_yaws(i) = last_yaw;
397 Eigen::ArrayXf yaw_cos = traj_yaws.cos();
398 Eigen::ArrayXf yaw_sin = traj_yaws.sin();
399 utils::shiftColumnsByOnePlace(yaw_cos, 1);
400 utils::shiftColumnsByOnePlace(yaw_sin, 1);
401 yaw_cos(0) = cosf(initial_yaw);
402 yaw_sin(0) = sinf(initial_yaw);
404 auto dx = (vx * yaw_cos).eval();
405 auto dy = (vx * yaw_sin).eval();
408 auto vy = sequence.col(2);
409 dx = (dx - vy * yaw_sin).eval();
410 dy = (dy + vy * yaw_cos).eval();
413 float last_x = state_.pose.pose.position.x;
414 float last_y = state_.pose.pose.position.y;
415 for(
size_t i = 0; i != n_size; i++) {
416 last_x += dx(i) * settings_.model_dt;
417 last_y += dy(i) * settings_.model_dt;
427 auto initial_yaw =
static_cast<float>(tf2::getYaw(state.pose.pose.orientation));
428 const size_t n_cols = trajectories.yaws.cols();
430 Eigen::ArrayXf last_yaws = Eigen::ArrayXf::Constant(trajectories.yaws.rows(), initial_yaw);
431 for (
size_t i = 0; i != n_cols; i++) {
432 last_yaws += state.wz.col(i) * settings_.model_dt;
433 trajectories.yaws.col(i) = last_yaws;
436 Eigen::ArrayXXf yaw_cos = trajectories.yaws.cos();
437 Eigen::ArrayXXf yaw_sin = trajectories.yaws.sin();
438 utils::shiftColumnsByOnePlace(yaw_cos, 1);
439 utils::shiftColumnsByOnePlace(yaw_sin, 1);
440 yaw_cos.col(0) = cosf(initial_yaw);
441 yaw_sin.col(0) = sinf(initial_yaw);
443 auto dx = (state.vx * yaw_cos).eval();
444 auto dy = (state.vx * yaw_sin).eval();
447 dx -= state.vy * yaw_sin;
448 dy += state.vy * yaw_cos;
451 Eigen::ArrayXf last_x = Eigen::ArrayXf::Constant(trajectories.x.rows(),
452 state.pose.pose.position.x);
453 Eigen::ArrayXf last_y = Eigen::ArrayXf::Constant(trajectories.y.rows(),
454 state.pose.pose.position.y);
456 for (
size_t i = 0; i != n_cols; i++) {
457 last_x += dx.col(i) * settings_.model_dt;
458 last_y += dy.col(i) * settings_.model_dt;
459 trajectories.x.col(i) = last_x;
460 trajectories.y.col(i) = last_y;
467 Eigen::ArrayXXf sequence = Eigen::ArrayXXf(settings_.time_steps, is_holo ? 3 : 2);
468 Eigen::Array<float, Eigen::Dynamic, 3> trajectories =
469 Eigen::Array<float, Eigen::Dynamic, 3>(settings_.time_steps, 3);
471 sequence.col(0) = control_sequence_.vx;
472 sequence.col(1) = control_sequence_.wz;
475 sequence.col(2) = control_sequence_.vy;
484 return control_sequence_;
490 auto & s = settings_;
492 auto vx_T = control_sequence_.vx.transpose();
493 auto bounded_noises_vx = state_.cvx.rowwise() - vx_T;
494 const float gamma_vx = s.gamma / (s.sampling_std.vx * s.sampling_std.vx);
495 costs_ += (gamma_vx * (bounded_noises_vx.rowwise() * vx_T).rowwise().sum()).eval();
497 if (s.sampling_std.wz > 0.0f) {
498 auto wz_T = control_sequence_.wz.transpose();
499 auto bounded_noises_wz = state_.cwz.rowwise() - wz_T;
500 const float gamma_wz = s.gamma / (s.sampling_std.wz * s.sampling_std.wz);
501 costs_ += (gamma_wz * (bounded_noises_wz.rowwise() * wz_T).rowwise().sum()).eval();
505 auto vy_T = control_sequence_.vy.transpose();
506 auto bounded_noises_vy = state_.cvy.rowwise() - vy_T;
507 const float gamma_vy = s.gamma / (s.sampling_std.vy * s.sampling_std.vy);
508 costs_ += (gamma_vy * (bounded_noises_vy.rowwise() * vy_T).rowwise().sum()).eval();
511 auto costs_normalized = costs_ - costs_.minCoeff();
512 const float inv_temp = 1.0f / s.temperature;
513 auto softmaxes = (-inv_temp * costs_normalized).exp().eval();
514 softmaxes /= softmaxes.sum();
516 auto softmax_mat = softmaxes.matrix();
517 control_sequence_.vx = state_.cvx.transpose().matrix() * softmax_mat;
518 control_sequence_.wz = state_.cwz.transpose().matrix() * softmax_mat;
521 control_sequence_.vy = state_.cvy.transpose().matrix() * softmax_mat;
528 const builtin_interfaces::msg::Time & stamp)
530 unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
532 auto vx = control_sequence_.vx(offset);
533 auto wz = control_sequence_.wz(offset);
536 auto vy = control_sequence_.vy(offset);
537 return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
540 return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
545 if (model ==
"DiffDrive") {
546 motion_model_ = std::make_shared<DiffDriveMotionModel>();
547 }
else if (model ==
"Omni") {
548 motion_model_ = std::make_shared<OmniMotionModel>();
549 }
else if (model ==
"Ackermann") {
550 motion_model_ = std::make_shared<AckermannMotionModel>(parameters_handler_, name_);
554 "Model " + model +
" is not valid! Valid options are DiffDrive, Omni, "
557 motion_model_->initialize(settings_.constraints, settings_.model_dt);
562 auto & s = settings_;
563 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
564 s.constraints.vx_max = s.base_constraints.vx_max;
565 s.constraints.vx_min = s.base_constraints.vx_min;
566 s.constraints.vy = s.base_constraints.vy;
567 s.constraints.wz = s.base_constraints.wz;
571 double ratio = speed_limit / 100.0;
572 s.constraints.vx_max = s.base_constraints.vx_max * ratio;
573 s.constraints.vx_min = s.base_constraints.vx_min * ratio;
574 s.constraints.vy = s.base_constraints.vy * ratio;
575 s.constraints.wz = s.base_constraints.wz * ratio;
578 double ratio = speed_limit / s.base_constraints.vx_max;
579 s.constraints.vx_max = s.base_constraints.vx_max * ratio;
580 s.constraints.vx_min = s.base_constraints.vx_min * ratio;
581 s.constraints.vy = s.base_constraints.vy * ratio;
582 s.constraints.wz = s.base_constraints.wz * ratio;
589 return generated_trajectories_;
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) const
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.
const models::ControlSequence & getOptimalControlSequence()
Get the optimal control sequence for a cycle for visualization.
void setMotionModel(const std::string &model)
Set the motion model of the vehicle platform.
void setOffset(double controller_frequency)
Using control frequencies and time step size, determine if trajectory offset should be used to popula...
Eigen::ArrayXXf getOptimizedTrajectory()
Get the optimal trajectory for a cycle for visualization.
void reset(bool reset_dynamic_speed_limits=true)
Reset the optimization problem to initial conditions.
rclcpp::Logger logger_
Caution, keep references.
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.
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.
void initialize(nav2::LifecycleNode::WeakPtr parent, const std::string &name, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros, std::shared_ptr< tf2_ros::Buffer > tf_buffer, ParametersHandler *dynamic_parameters_handler)
Initializes optimizer on startup.
void integrateStateVelocities(models::Trajectories &trajectories, const models::State &state) const
Rollout velocities in state to poses.
void updateControlSequence()
Update control sequence with state controls weighted by costs using softmax function.
void generateNoisedTrajectories()
updates generated trajectories with noised trajectories from the last cycle's optimal control
bool fallback(bool fail)
Perform fallback behavior to try to recover from a set of trajectories in collision.
bool isHolonomic() const
Whether the motion model is holonomic.
models::Trajectories & getGeneratedTrajectories()
Get the trajectories generated in a cycle for visualization.
void updateInitialStateVelocities(models::State &state) const
Update initial velocity in state.
void shutdown()
Shutdown for optimizer at process end.
void optimize()
Main function to generate, score, and return trajectories.
void setSpeedLimit(double speed_limit, bool percentage)
Set the maximum speed based on the speed limits callback.
void shiftControlSequence()
Shift the optimal control sequence after processing for next iterations initial conditions after exec...
void applyControlSequenceConstraints()
Apply hard vehicle constraints on control sequence.
void getParams()
Obtain the main controller's parameters.
void propagateStateVelocitiesFromInitials(models::State &state) const
predict velocities in state using model for time horizon equal to timesteps
geometry_msgs::msg::TwistStamped getControlFromSequenceAsTwist(const builtin_interfaces::msg::Time &stamp)
Convert control sequence to a twist command.
Handles getting parameters and dynamic parameter changes.
void addPostCallback(T &&callback)
Set a callback to process after 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.
void reset(unsigned int batch_size, unsigned int time_steps)
Reset state data.
void reset(unsigned int batch_size, unsigned int time_steps)
Reset state data.