15 #include "nav2_mppi_controller/optimizer.hpp"
23 #include <xtensor/xmath.hpp>
24 #include <xtensor/xrandom.hpp>
25 #include <xtensor/xnoalias.hpp>
27 #include "nav2_core/controller_exceptions.hpp"
28 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
33 using namespace xt::placeholders;
34 using xt::evaluation_strategy::immediate;
37 rclcpp_lifecycle::LifecycleNode::WeakPtr parent,
const std::string & name,
38 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
43 costmap_ros_ = costmap_ros;
44 costmap_ = costmap_ros_->getCostmap();
45 parameters_handler_ = param_handler;
47 auto node = parent_.lock();
48 logger_ = node->get_logger();
52 critic_manager_.on_configure(parent_, name_, costmap_ros_, parameters_handler_);
53 noise_generator_.initialize(settings_, isHolonomic(), name_, parameters_handler_);
60 noise_generator_.shutdown();
65 std::string motion_model_name;
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);
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;
95 "Sign of the parameter ax_min is incorrect, consider setting it negative.");
98 if (s.base_constraints.ay_min > 0.0) {
99 s.base_constraints.ay_min = -1.0 * s.base_constraints.ay_min;
102 "Sign of the parameter ay_min is incorrect, consider setting it negative.");
105 getParam(motion_model_name,
"motion_model", std::string(
"DiffDrive"));
107 s.constraints = s.base_constraints;
108 setMotionModel(motion_model_name);
109 parameters_handler_->addPostCallback([
this]() {reset();});
111 double controller_frequency;
112 getParentParam(controller_frequency,
"controller_frequency", 0.0, ParameterType::Static);
113 setOffset(controller_frequency);
118 const double controller_period = 1.0 / controller_frequency;
119 constexpr
double eps = 1e-6;
121 if ((controller_period + eps) < settings_.model_dt) {
124 "Controller period is less then model dt, consider setting it equal");
125 }
else if (abs(controller_period - settings_.model_dt) < eps) {
128 "Controller period is equal to model dt. Control sequence "
130 settings_.shift_control_sequence =
true;
133 "Controller period more then model dt, set it equal to model dt");
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};
146 if (reset_dynamic_speed_limits) {
147 settings_.constraints = settings_.base_constraints;
150 costs_ = xt::zeros<float>({settings_.batch_size});
151 generated_trajectories_.reset(settings_.batch_size, settings_.time_steps);
153 noise_generator_.reset(settings_, isHolonomic());
154 motion_model_->initialize(settings_.constraints, settings_.model_dt);
156 RCLCPP_INFO(logger_,
"Optimizer reset");
161 return motion_model_->isHolonomic();
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,
171 prepare(robot_pose, robot_speed, plan, goal, goal_checker);
175 }
while (fallback(critics_data_.fail_flag));
177 utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
178 auto control = getControlFromSequenceAsTwist(plan.header.stamp);
180 if (settings_.shift_control_sequence) {
181 shiftControlSequence();
189 for (
size_t i = 0; i < settings_.iteration_count; ++i) {
190 generateNoisedTrajectories();
191 critic_manager_.evalTrajectoriesScores(critics_data_);
192 updateControlSequence();
198 static size_t counter = 0;
207 if (++counter > settings_.retry_attempt_limit) {
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,
222 state_.pose = robot_pose;
223 state_.speed = robot_speed;
224 path_ = utils::toTensor(plan);
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();
237 using namespace xt::placeholders;
238 control_sequence_.vx = xt::roll(control_sequence_.vx, -1);
239 control_sequence_.wz = xt::roll(control_sequence_.wz, -1);
242 xt::view(control_sequence_.vx, -1) =
243 xt::view(control_sequence_.vx, -2);
245 xt::view(control_sequence_.wz, -1) =
246 xt::view(control_sequence_.wz, -2);
250 control_sequence_.vy = xt::roll(control_sequence_.vy, -1);
251 xt::view(control_sequence_.vy, -1) =
252 xt::view(control_sequence_.vy, -2);
258 noise_generator_.setNoisedControls(state_, control_sequence_);
259 noise_generator_.generateNextNoises();
260 updateStateVelocities(state_);
261 integrateStateVelocities(generated_trajectories_, state_);
266 auto & s = settings_;
269 control_sequence_.vy = xt::clip(control_sequence_.vy, -s.constraints.vy, s.constraints.vy);
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);
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);
286 vx_curr = std::clamp(vx_curr, vx_last + min_delta_vx, vx_last + max_delta_vx);
288 vx_curr = std::clamp(vx_curr, vx_last - max_delta_vx, vx_last - min_delta_vx);
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);
297 float & vy_curr = control_sequence_.vy(i);
299 vy_curr = std::clamp(vy_curr, vy_last + min_delta_vy, vy_last + max_delta_vy);
301 vy_curr = std::clamp(vy_curr, vy_last - max_delta_vy, vy_last - min_delta_vy);
307 motion_model_->applyConstraints(control_sequence_);
313 updateInitialStateVelocities(state);
314 propagateStateVelocitiesFromInitials(state);
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);
324 xt::noalias(xt::view(state.vy, xt::all(), 0)) =
static_cast<float>(state.speed.linear.y);
331 motion_model_->predict(state);
335 xt::xtensor<float, 2> & trajectory,
336 const xt::xtensor<float, 2> & sequence)
const
338 float initial_yaw =
static_cast<float>(tf2::getYaw(state_.pose.pose.orientation));
340 const auto vx = xt::view(sequence, xt::all(), 0);
341 const auto wz = xt::view(sequence, xt::all(), 1);
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);
347 xt::noalias(traj_yaws) = xt::cumsum(wz * settings_.model_dt, 0) + initial_yaw;
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);
354 auto && dx = xt::eval(vx * yaw_cos);
355 auto && dy = xt::eval(vx * yaw_sin);
358 const auto vy = xt::view(sequence, xt::all(), 2);
359 dx = dx - vy * yaw_sin;
360 dy = dy + vy * yaw_cos;
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);
371 const float initial_yaw =
static_cast<float>(tf2::getYaw(state.pose.pose.orientation));
373 xt::noalias(trajectories.yaws) =
374 xt::cumsum(state.wz * settings_.model_dt, {1}) + initial_yaw;
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);
381 auto && dx = xt::eval(state.vx * yaw_cos);
382 auto && dy = xt::eval(state.vx * yaw_sin);
385 dx = dx - state.vy * yaw_sin;
386 dy = dy + state.vy * yaw_cos;
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});
397 const bool is_holo = isHolonomic();
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});
402 xt::noalias(xt::view(sequence, xt::all(), 0)) = control_sequence_.vx;
403 xt::noalias(xt::view(sequence, xt::all(), 1)) = control_sequence_.wz;
406 xt::noalias(xt::view(sequence, xt::all(), 2)) = control_sequence_.vy;
409 integrateStateVelocities(trajectories, sequence);
410 return std::move(trajectories);
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);
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,
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()));
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);
442 xt::noalias(control_sequence_.vy) = xt::sum(state_.cvy * softmaxes_extened, 0, immediate);
445 applyControlSequenceConstraints();
449 const builtin_interfaces::msg::Time & stamp)
451 unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
453 auto vx = control_sequence_.vx(offset);
454 auto wz = control_sequence_.wz(offset);
457 auto vy = control_sequence_.vy(offset);
458 return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
461 return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
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_);
475 "Model " + model +
" is not valid! Valid options are DiffDrive, Omni, "
478 motion_model_->initialize(settings_.constraints, settings_.model_dt);
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;
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;
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;
510 return generated_trajectories_;
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.
void updateStateVelocities(models::State &state) const
Update velocities in state.
void setMotionModel(const std::string &model)
Set the motion model of the vehicle platform.
void setOffset(double controller_frequency)
Using control frequence and time step size, determine if trajectory offset should be used to populate...
void reset(bool reset_dynamic_speed_limits=true)
Reset the optimization problem to initial conditions.
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.
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.
xt::xtensor< float, 2 > getOptimizedTrajectory()
Get the optimal trajectory for a cycle for visualization.
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
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.
geometry_msgs::msg::TwistStamped getControlFromSequenceAsTwist(const builtin_interfaces::msg::Time &stamp)
Convert control sequence to a twist commant.
Handles getting parameters and dynamic parmaeter changes.
Function-object for checking whether a goal has been reached.
State information: velocities, controls, poses, speed.