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);
103 s.base_constraints.ax_max = fabs(s.base_constraints.ax_max);
104 if (s.base_constraints.ax_min > 0.0) {
105 s.base_constraints.ax_min = -1.0 * s.base_constraints.ax_min;
108 "Sign of the parameter ax_min is incorrect, consider setting it negative.");
111 if (s.base_constraints.ay_min > 0.0) {
112 s.base_constraints.ay_min = -1.0 * s.base_constraints.ay_min;
115 "Sign of the parameter ay_min is incorrect, consider setting it negative.");
119 getParam(motion_model_name,
"motion_model", std::string(
"DiffDrive"));
121 s.constraints = s.base_constraints;
126 double controller_frequency;
127 getParentParam(controller_frequency,
"controller_frequency", 0.0, ParameterType::Static);
133 const double controller_period = 1.0 / controller_frequency;
134 constexpr
double eps = 1e-6;
136 if ((controller_period + eps) < settings_.model_dt) {
139 "Controller period is less then model dt, consider setting it equal");
140 }
else if (abs(controller_period - settings_.model_dt) < eps) {
143 "Controller period is equal to model dt. Control sequence "
145 settings_.shift_control_sequence =
true;
148 "Controller period more then model dt, set it equal to model dt");
154 state_.
reset(settings_.batch_size, settings_.time_steps);
155 control_sequence_.reset(settings_.time_steps);
156 control_history_[0] = {0.0f, 0.0f, 0.0f};
157 control_history_[1] = {0.0f, 0.0f, 0.0f};
158 control_history_[2] = {0.0f, 0.0f, 0.0f};
159 control_history_[3] = {0.0f, 0.0f, 0.0f};
161 if (reset_dynamic_speed_limits) {
162 settings_.constraints = settings_.base_constraints;
165 costs_.setZero(settings_.batch_size);
166 generated_trajectories_.
reset(settings_.batch_size, settings_.time_steps);
169 motion_model_->initialize(settings_.constraints, settings_.model_dt);
170 trajectory_validator_->initialize(
171 parent_, name_ +
".TrajectoryValidator",
172 costmap_ros_, parameters_handler_, tf_buffer_, settings_);
174 RCLCPP_INFO(
logger_,
"Optimizer reset");
179 return motion_model_->isHolonomic();
183 const geometry_msgs::msg::PoseStamped & robot_pose,
184 const geometry_msgs::msg::Twist & robot_speed,
185 const nav_msgs::msg::Path & plan,
186 const geometry_msgs::msg::Pose & goal,
189 prepare(robot_pose, robot_speed, plan, goal, goal_checker);
190 Eigen::ArrayXXf optimal_trajectory;
191 bool trajectory_valid =
true;
196 switch (trajectory_validator_->validateTrajectory(
197 optimal_trajectory, control_sequence_, robot_pose, robot_speed, plan, goal))
199 case mppi::ValidationResult::SOFT_RESET:
200 trajectory_valid =
false;
201 RCLCPP_WARN(
logger_,
"Soft reset triggered by trajectory validator");
203 case mppi::ValidationResult::FAILURE:
205 "Trajectory validator failed to validate trajectory, hard reset triggered.");
206 case mppi::ValidationResult::SUCCESS:
208 trajectory_valid =
true;
211 }
while (
fallback(critics_data_.fail_flag || !trajectory_valid));
213 utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
216 if (settings_.shift_control_sequence) {
220 return std::make_tuple(control, optimal_trajectory);
225 for (
size_t i = 0; i < settings_.iteration_count; ++i) {
234 static size_t counter = 0;
243 if (++counter > settings_.retry_attempt_limit) {
252 const geometry_msgs::msg::PoseStamped & robot_pose,
253 const geometry_msgs::msg::Twist & robot_speed,
254 const nav_msgs::msg::Path & plan,
255 const geometry_msgs::msg::Pose & goal,
258 state_.pose = robot_pose;
259 state_.speed = robot_speed;
260 state_.local_path_length = nav2_util::geometry_utils::calculate_path_length(plan);
261 path_ = utils::toTensor(plan);
265 critics_data_.fail_flag =
false;
266 critics_data_.goal_checker = goal_checker;
267 critics_data_.motion_model = motion_model_;
268 critics_data_.furthest_reached_path_point.reset();
269 critics_data_.path_pts_valid.reset();
274 auto size = control_sequence_.vx.size();
275 utils::shiftColumnsByOnePlace(control_sequence_.vx, -1);
276 utils::shiftColumnsByOnePlace(control_sequence_.wz, -1);
277 control_sequence_.vx(size - 1) = control_sequence_.vx(size - 2);
278 control_sequence_.wz(size - 1) = control_sequence_.wz(size - 2);
281 utils::shiftColumnsByOnePlace(control_sequence_.vy, -1);
282 control_sequence_.vy(size - 1) = control_sequence_.vy(size - 2);
296 auto & s = settings_;
298 float max_delta_vx = s.model_dt * s.constraints.ax_max;
299 float min_delta_vx = s.model_dt * s.constraints.ax_min;
300 float max_delta_vy = s.model_dt * s.constraints.ay_max;
301 float min_delta_vy = s.model_dt * s.constraints.ay_min;
302 float max_delta_wz = s.model_dt * s.constraints.az_max;
303 float vx_last = utils::clamp(s.constraints.vx_min, s.constraints.vx_max, control_sequence_.vx(0));
304 float wz_last = utils::clamp(-s.constraints.wz, s.constraints.wz, control_sequence_.wz(0));
305 control_sequence_.vx(0) = vx_last;
306 control_sequence_.wz(0) = wz_last;
309 vy_last = utils::clamp(-s.constraints.vy, s.constraints.vy, control_sequence_.vy(0));
310 control_sequence_.vy(0) = vy_last;
313 for (
unsigned int i = 1; i != control_sequence_.vx.size(); i++) {
314 float & vx_curr = control_sequence_.vx(i);
315 vx_curr = utils::clamp(s.constraints.vx_min, s.constraints.vx_max, vx_curr);
317 vx_curr = utils::clamp(vx_last + min_delta_vx, vx_last + max_delta_vx, vx_curr);
319 vx_curr = utils::clamp(vx_last - max_delta_vx, vx_last - min_delta_vx, vx_curr);
323 float & wz_curr = control_sequence_.wz(i);
324 wz_curr = utils::clamp(-s.constraints.wz, s.constraints.wz, wz_curr);
325 wz_curr = utils::clamp(wz_last - max_delta_wz, wz_last + max_delta_wz, wz_curr);
329 float & vy_curr = control_sequence_.vy(i);
330 vy_curr = utils::clamp(-s.constraints.vy, s.constraints.vy, vy_curr);
332 vy_curr = utils::clamp(vy_last + min_delta_vy, vy_last + max_delta_vy, vy_curr);
334 vy_curr = utils::clamp(vy_last - max_delta_vy, vy_last - min_delta_vy, vy_curr);
340 motion_model_->applyConstraints(control_sequence_);
353 state.vx.col(0) =
static_cast<float>(state.speed.linear.x);
354 state.wz.col(0) =
static_cast<float>(state.speed.angular.z);
357 state.vy.col(0) =
static_cast<float>(state.speed.linear.y);
364 motion_model_->predict(state);
368 Eigen::Array<float, Eigen::Dynamic, 3> & trajectory,
369 const Eigen::ArrayXXf & sequence)
const
371 float initial_yaw =
static_cast<float>(tf2::getYaw(state_.pose.pose.orientation));
373 const auto vx = sequence.col(0);
374 const auto wz = sequence.col(1);
376 auto traj_x = trajectory.col(0);
377 auto traj_y = trajectory.col(1);
378 auto traj_yaws = trajectory.col(2);
380 const size_t n_size = traj_yaws.size();
385 float last_yaw = initial_yaw;
386 for(
size_t i = 0; i != n_size; i++) {
387 last_yaw += wz(i) * settings_.model_dt;
388 traj_yaws(i) = last_yaw;
391 Eigen::ArrayXf yaw_cos = traj_yaws.cos();
392 Eigen::ArrayXf yaw_sin = traj_yaws.sin();
393 utils::shiftColumnsByOnePlace(yaw_cos, 1);
394 utils::shiftColumnsByOnePlace(yaw_sin, 1);
395 yaw_cos(0) = cosf(initial_yaw);
396 yaw_sin(0) = sinf(initial_yaw);
398 auto dx = (vx * yaw_cos).eval();
399 auto dy = (vx * yaw_sin).eval();
402 auto vy = sequence.col(2);
403 dx = (dx - vy * yaw_sin).eval();
404 dy = (dy + vy * yaw_cos).eval();
407 float last_x = state_.pose.pose.position.x;
408 float last_y = state_.pose.pose.position.y;
409 for(
size_t i = 0; i != n_size; i++) {
410 last_x += dx(i) * settings_.model_dt;
411 last_y += dy(i) * settings_.model_dt;
421 auto initial_yaw =
static_cast<float>(tf2::getYaw(state.pose.pose.orientation));
422 const size_t n_cols = trajectories.yaws.cols();
424 Eigen::ArrayXf last_yaws = Eigen::ArrayXf::Constant(trajectories.yaws.rows(), initial_yaw);
425 for (
size_t i = 0; i != n_cols; i++) {
426 last_yaws += state.wz.col(i) * settings_.model_dt;
427 trajectories.yaws.col(i) = last_yaws;
430 Eigen::ArrayXXf yaw_cos = trajectories.yaws.cos();
431 Eigen::ArrayXXf yaw_sin = trajectories.yaws.sin();
432 utils::shiftColumnsByOnePlace(yaw_cos, 1);
433 utils::shiftColumnsByOnePlace(yaw_sin, 1);
434 yaw_cos.col(0) = cosf(initial_yaw);
435 yaw_sin.col(0) = sinf(initial_yaw);
437 auto dx = (state.vx * yaw_cos).eval();
438 auto dy = (state.vx * yaw_sin).eval();
441 dx -= state.vy * yaw_sin;
442 dy += state.vy * yaw_cos;
445 Eigen::ArrayXf last_x = Eigen::ArrayXf::Constant(trajectories.x.rows(),
446 state.pose.pose.position.x);
447 Eigen::ArrayXf last_y = Eigen::ArrayXf::Constant(trajectories.y.rows(),
448 state.pose.pose.position.y);
450 for (
size_t i = 0; i != n_cols; i++) {
451 last_x += dx.col(i) * settings_.model_dt;
452 last_y += dy.col(i) * settings_.model_dt;
453 trajectories.x.col(i) = last_x;
454 trajectories.y.col(i) = last_y;
461 Eigen::ArrayXXf sequence = Eigen::ArrayXXf(settings_.time_steps, is_holo ? 3 : 2);
462 Eigen::Array<float, Eigen::Dynamic, 3> trajectories =
463 Eigen::Array<float, Eigen::Dynamic, 3>(settings_.time_steps, 3);
465 sequence.col(0) = control_sequence_.vx;
466 sequence.col(1) = control_sequence_.wz;
469 sequence.col(2) = control_sequence_.vy;
478 return control_sequence_;
484 auto & s = settings_;
486 auto vx_T = control_sequence_.vx.transpose();
487 auto bounded_noises_vx = state_.cvx.rowwise() - vx_T;
488 const float gamma_vx = s.gamma / (s.sampling_std.vx * s.sampling_std.vx);
489 costs_ += (gamma_vx * (bounded_noises_vx.rowwise() * vx_T).rowwise().sum()).eval();
491 if (s.sampling_std.wz > 0.0f) {
492 auto wz_T = control_sequence_.wz.transpose();
493 auto bounded_noises_wz = state_.cwz.rowwise() - wz_T;
494 const float gamma_wz = s.gamma / (s.sampling_std.wz * s.sampling_std.wz);
495 costs_ += (gamma_wz * (bounded_noises_wz.rowwise() * wz_T).rowwise().sum()).eval();
499 auto vy_T = control_sequence_.vy.transpose();
500 auto bounded_noises_vy = state_.cvy.rowwise() - vy_T;
501 const float gamma_vy = s.gamma / (s.sampling_std.vy * s.sampling_std.vy);
502 costs_ += (gamma_vy * (bounded_noises_vy.rowwise() * vy_T).rowwise().sum()).eval();
505 auto costs_normalized = costs_ - costs_.minCoeff();
506 const float inv_temp = 1.0f / s.temperature;
507 auto softmaxes = (-inv_temp * costs_normalized).exp().eval();
508 softmaxes /= softmaxes.sum();
510 auto softmax_mat = softmaxes.matrix();
511 control_sequence_.vx = state_.cvx.transpose().matrix() * softmax_mat;
512 control_sequence_.wz = state_.cwz.transpose().matrix() * softmax_mat;
515 control_sequence_.vy = state_.cvy.transpose().matrix() * softmax_mat;
522 const builtin_interfaces::msg::Time & stamp)
524 unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
526 auto vx = control_sequence_.vx(offset);
527 auto wz = control_sequence_.wz(offset);
530 auto vy = control_sequence_.vy(offset);
531 return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
534 return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
539 if (model ==
"DiffDrive") {
540 motion_model_ = std::make_shared<DiffDriveMotionModel>();
541 }
else if (model ==
"Omni") {
542 motion_model_ = std::make_shared<OmniMotionModel>();
543 }
else if (model ==
"Ackermann") {
544 motion_model_ = std::make_shared<AckermannMotionModel>(parameters_handler_, name_);
548 "Model " + model +
" is not valid! Valid options are DiffDrive, Omni, "
551 motion_model_->initialize(settings_.constraints, settings_.model_dt);
556 auto & s = settings_;
557 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
558 s.constraints.vx_max = s.base_constraints.vx_max;
559 s.constraints.vx_min = s.base_constraints.vx_min;
560 s.constraints.vy = s.base_constraints.vy;
561 s.constraints.wz = s.base_constraints.wz;
565 double ratio = speed_limit / 100.0;
566 s.constraints.vx_max = s.base_constraints.vx_max * ratio;
567 s.constraints.vx_min = s.base_constraints.vx_min * ratio;
568 s.constraints.vy = s.base_constraints.vy * ratio;
569 s.constraints.wz = s.base_constraints.wz * ratio;
572 double ratio = speed_limit / s.base_constraints.vx_max;
573 s.constraints.vx_max = s.base_constraints.vx_max * ratio;
574 s.constraints.vx_min = s.base_constraints.vx_min * ratio;
575 s.constraints.vy = s.base_constraints.vy * ratio;
576 s.constraints.wz = s.base_constraints.wz * ratio;
583 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.