Files
agv_pro_ros2/navigation2/nav2_mppi_controller/src/optimizer.cpp
T
2025-05-27 19:03:40 +08:00

461 lines
15 KiB
C++

// Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_mppi_controller/optimizer.hpp"
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include <cmath>
#include <xtensor/xmath.hpp>
#include <xtensor/xrandom.hpp>
#include <xtensor/xnoalias.hpp>
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
namespace mppi
{
using namespace xt::placeholders; // NOLINT
using xt::evaluation_strategy::immediate;
void Optimizer::initialize(
rclcpp_lifecycle::LifecycleNode::WeakPtr parent, const std::string & name,
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
ParametersHandler * param_handler)
{
parent_ = parent;
name_ = name;
costmap_ros_ = costmap_ros;
costmap_ = costmap_ros_->getCostmap();
parameters_handler_ = param_handler;
auto node = parent_.lock();
logger_ = node->get_logger();
getParams();
critic_manager_.on_configure(parent_, name_, costmap_ros_, parameters_handler_);
noise_generator_.initialize(settings_, isHolonomic(), name_, parameters_handler_);
reset();
}
void Optimizer::shutdown()
{
noise_generator_.shutdown();
}
void Optimizer::getParams()
{
std::string motion_model_name;
auto & s = settings_;
auto getParam = parameters_handler_->getParamGetter(name_);
auto getParentParam = parameters_handler_->getParamGetter("");
getParam(s.model_dt, "model_dt", 0.05f);
getParam(s.time_steps, "time_steps", 56);
getParam(s.batch_size, "batch_size", 1000);
getParam(s.iteration_count, "iteration_count", 1);
getParam(s.temperature, "temperature", 0.3f);
getParam(s.gamma, "gamma", 0.015f);
getParam(s.base_constraints.vx_max, "vx_max", 0.5);
getParam(s.base_constraints.vx_min, "vx_min", -0.35);
getParam(s.base_constraints.vy, "vy_max", 0.5);
getParam(s.base_constraints.wz, "wz_max", 1.9);
getParam(s.sampling_std.vx, "vx_std", 0.2);
getParam(s.sampling_std.vy, "vy_std", 0.2);
getParam(s.sampling_std.wz, "wz_std", 0.4);
getParam(s.retry_attempt_limit, "retry_attempt_limit", 1);
getParam(motion_model_name, "motion_model", std::string("DiffDrive"));
s.constraints = s.base_constraints;
setMotionModel(motion_model_name);
parameters_handler_->addPostCallback([this]() {reset();});
double controller_frequency;
getParentParam(controller_frequency, "controller_frequency", 0.0, ParameterType::Static);
setOffset(controller_frequency);
}
void Optimizer::setOffset(double controller_frequency)
{
const double controller_period = 1.0 / controller_frequency;
constexpr double eps = 1e-6;
if ((controller_period + eps) < settings_.model_dt) {
RCLCPP_WARN(
logger_,
"Controller period is less then model dt, consider setting it equal");
} else if (abs(controller_period - settings_.model_dt) < eps) {
RCLCPP_INFO(
logger_,
"Controller period is equal to model dt. Control sequence "
"shifting is ON");
settings_.shift_control_sequence = true;
} else {
throw std::runtime_error(
"Controller period more then model dt, set it equal to model dt");
}
}
void Optimizer::reset()
{
state_.reset(settings_.batch_size, settings_.time_steps);
control_sequence_.reset(settings_.time_steps);
control_history_[0] = {0.0, 0.0, 0.0};
control_history_[1] = {0.0, 0.0, 0.0};
control_history_[2] = {0.0, 0.0, 0.0};
control_history_[3] = {0.0, 0.0, 0.0};
settings_.constraints = settings_.base_constraints;
costs_ = xt::zeros<float>({settings_.batch_size});
generated_trajectories_.reset(settings_.batch_size, settings_.time_steps);
noise_generator_.reset(settings_, isHolonomic());
RCLCPP_INFO(logger_, "Optimizer reset");
}
geometry_msgs::msg::TwistStamped Optimizer::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)
{
prepare(robot_pose, robot_speed, plan, goal, goal_checker);
do {
optimize();
} while (fallback(critics_data_.fail_flag));
utils::savitskyGolayFilter(control_sequence_, control_history_, settings_);
auto control = getControlFromSequenceAsTwist(plan.header.stamp);
if (settings_.shift_control_sequence) {
shiftControlSequence();
}
return control;
}
void Optimizer::optimize()
{
for (size_t i = 0; i < settings_.iteration_count; ++i) {
generateNoisedTrajectories();
critic_manager_.evalTrajectoriesScores(critics_data_);
updateControlSequence();
}
}
bool Optimizer::fallback(bool fail)
{
static size_t counter = 0;
if (!fail) {
counter = 0;
return false;
}
reset();
if (++counter > settings_.retry_attempt_limit) {
counter = 0;
throw std::runtime_error("Optimizer fail to compute path");
}
return true;
}
void Optimizer::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)
{
state_.pose = robot_pose;
state_.speed = robot_speed;
path_ = utils::toTensor(plan);
goal_ = goal;
costs_.fill(0);
critics_data_.fail_flag = false;
critics_data_.goal_checker = goal_checker;
critics_data_.motion_model = motion_model_;
critics_data_.furthest_reached_path_point.reset();
critics_data_.path_pts_valid.reset();
}
void Optimizer::shiftControlSequence()
{
using namespace xt::placeholders; // NOLINT
control_sequence_.vx = xt::roll(control_sequence_.vx, -1);
control_sequence_.wz = xt::roll(control_sequence_.wz, -1);
xt::view(control_sequence_.vx, -1) =
xt::view(control_sequence_.vx, -2);
xt::view(control_sequence_.wz, -1) =
xt::view(control_sequence_.wz, -2);
if (isHolonomic()) {
control_sequence_.vy = xt::roll(control_sequence_.vy, -1);
xt::view(control_sequence_.vy, -1) =
xt::view(control_sequence_.vy, -2);
}
}
void Optimizer::generateNoisedTrajectories()
{
noise_generator_.setNoisedControls(state_, control_sequence_);
noise_generator_.generateNextNoises();
updateStateVelocities(state_);
integrateStateVelocities(generated_trajectories_, state_);
}
bool Optimizer::isHolonomic() const {return motion_model_->isHolonomic();}
void Optimizer::applyControlSequenceConstraints()
{
auto & s = settings_;
if (isHolonomic()) {
control_sequence_.vy = xt::clip(control_sequence_.vy, -s.constraints.vy, s.constraints.vy);
}
control_sequence_.vx = xt::clip(control_sequence_.vx, s.constraints.vx_min, s.constraints.vx_max);
control_sequence_.wz = xt::clip(control_sequence_.wz, -s.constraints.wz, s.constraints.wz);
motion_model_->applyConstraints(control_sequence_);
}
void Optimizer::updateStateVelocities(
models::State & state) const
{
updateInitialStateVelocities(state);
propagateStateVelocitiesFromInitials(state);
}
void Optimizer::updateInitialStateVelocities(
models::State & state) const
{
xt::noalias(xt::view(state.vx, xt::all(), 0)) = state.speed.linear.x;
xt::noalias(xt::view(state.wz, xt::all(), 0)) = state.speed.angular.z;
if (isHolonomic()) {
xt::noalias(xt::view(state.vy, xt::all(), 0)) = state.speed.linear.y;
}
}
void Optimizer::propagateStateVelocitiesFromInitials(
models::State & state) const
{
motion_model_->predict(state);
}
void Optimizer::integrateStateVelocities(
xt::xtensor<float, 2> & trajectory,
const xt::xtensor<float, 2> & sequence) const
{
float initial_yaw = tf2::getYaw(state_.pose.pose.orientation);
const auto vx = xt::view(sequence, xt::all(), 0);
const auto vy = xt::view(sequence, xt::all(), 2);
const auto wz = xt::view(sequence, xt::all(), 1);
auto traj_x = xt::view(trajectory, xt::all(), 0);
auto traj_y = xt::view(trajectory, xt::all(), 1);
auto traj_yaws = xt::view(trajectory, xt::all(), 2);
xt::noalias(traj_yaws) = xt::cumsum(wz * settings_.model_dt, 0) + initial_yaw;
auto && yaw_cos = xt::xtensor<float, 1>::from_shape(traj_yaws.shape());
auto && yaw_sin = xt::xtensor<float, 1>::from_shape(traj_yaws.shape());
const auto yaw_offseted = xt::view(traj_yaws, xt::range(1, _));
xt::noalias(xt::view(yaw_cos, 0)) = cosf(initial_yaw);
xt::noalias(xt::view(yaw_sin, 0)) = sinf(initial_yaw);
xt::noalias(xt::view(yaw_cos, xt::range(1, _))) = xt::cos(yaw_offseted);
xt::noalias(xt::view(yaw_sin, xt::range(1, _))) = xt::sin(yaw_offseted);
auto && dx = xt::eval(vx * yaw_cos);
auto && dy = xt::eval(vx * yaw_sin);
if (isHolonomic()) {
dx = dx - vy * yaw_sin;
dy = dy + vy * yaw_cos;
}
xt::noalias(traj_x) = state_.pose.pose.position.x + xt::cumsum(dx * settings_.model_dt, 0);
xt::noalias(traj_y) = state_.pose.pose.position.y + xt::cumsum(dy * settings_.model_dt, 0);
}
void Optimizer::integrateStateVelocities(
models::Trajectories & trajectories,
const models::State & state) const
{
const float initial_yaw = tf2::getYaw(state.pose.pose.orientation);
xt::noalias(trajectories.yaws) =
xt::cumsum(state.wz * settings_.model_dt, 1) + initial_yaw;
const auto yaws_cutted = xt::view(trajectories.yaws, xt::all(), xt::range(0, -1));
auto && yaw_cos = xt::xtensor<float, 2>::from_shape(trajectories.yaws.shape());
auto && yaw_sin = xt::xtensor<float, 2>::from_shape(trajectories.yaws.shape());
xt::noalias(xt::view(yaw_cos, xt::all(), 0)) = cosf(initial_yaw);
xt::noalias(xt::view(yaw_sin, xt::all(), 0)) = sinf(initial_yaw);
xt::noalias(xt::view(yaw_cos, xt::all(), xt::range(1, _))) = xt::cos(yaws_cutted);
xt::noalias(xt::view(yaw_sin, xt::all(), xt::range(1, _))) = xt::sin(yaws_cutted);
auto && dx = xt::eval(state.vx * yaw_cos);
auto && dy = xt::eval(state.vx * yaw_sin);
if (isHolonomic()) {
dx = dx - state.vy * yaw_sin;
dy = dy + state.vy * yaw_cos;
}
xt::noalias(trajectories.x) = state.pose.pose.position.x +
xt::cumsum(dx * settings_.model_dt, 1);
xt::noalias(trajectories.y) = state.pose.pose.position.y +
xt::cumsum(dy * settings_.model_dt, 1);
}
xt::xtensor<float, 2> Optimizer::getOptimizedTrajectory()
{
auto && sequence =
xt::xtensor<float, 2>::from_shape({settings_.time_steps, isHolonomic() ? 3u : 2u});
auto && trajectories = xt::xtensor<float, 2>::from_shape({settings_.time_steps, 3});
xt::noalias(xt::view(sequence, xt::all(), 0)) = control_sequence_.vx;
xt::noalias(xt::view(sequence, xt::all(), 1)) = control_sequence_.wz;
if (isHolonomic()) {
xt::noalias(xt::view(sequence, xt::all(), 2)) = control_sequence_.vy;
}
integrateStateVelocities(trajectories, sequence);
return std::move(trajectories);
}
void Optimizer::updateControlSequence()
{
auto & s = settings_;
auto bounded_noises_vx = state_.cvx - control_sequence_.vx;
auto bounded_noises_wz = state_.cwz - control_sequence_.wz;
xt::noalias(costs_) +=
s.gamma / powf(s.sampling_std.vx, 2) * xt::sum(
xt::view(control_sequence_.vx, xt::newaxis(), xt::all()) * bounded_noises_vx, 1, immediate);
xt::noalias(costs_) +=
s.gamma / powf(s.sampling_std.wz, 2) * xt::sum(
xt::view(control_sequence_.wz, xt::newaxis(), xt::all()) * bounded_noises_wz, 1, immediate);
if (isHolonomic()) {
auto bounded_noises_vy = state_.cvy - control_sequence_.vy;
xt::noalias(costs_) +=
s.gamma / powf(s.sampling_std.vy, 2) * xt::sum(
xt::view(control_sequence_.vy, xt::newaxis(), xt::all()) * bounded_noises_vy,
1, immediate);
}
auto && costs_normalized = costs_ - xt::amin(costs_, immediate);
auto && exponents = xt::eval(xt::exp(-1 / settings_.temperature * costs_normalized));
auto && softmaxes = xt::eval(exponents / xt::sum(exponents, immediate));
auto && softmaxes_extened = xt::eval(xt::view(softmaxes, xt::all(), xt::newaxis()));
xt::noalias(control_sequence_.vx) = xt::sum(state_.cvx * softmaxes_extened, 0, immediate);
xt::noalias(control_sequence_.wz) = xt::sum(state_.cwz * softmaxes_extened, 0, immediate);
if (isHolonomic()) {
xt::noalias(control_sequence_.vy) = xt::sum(state_.cvy * softmaxes_extened, 0, immediate);
}
applyControlSequenceConstraints();
}
geometry_msgs::msg::TwistStamped Optimizer::getControlFromSequenceAsTwist(
const builtin_interfaces::msg::Time & stamp)
{
unsigned int offset = settings_.shift_control_sequence ? 1 : 0;
auto vx = control_sequence_.vx(offset);
auto wz = control_sequence_.wz(offset);
if (isHolonomic()) {
auto vy = control_sequence_.vy(offset);
return utils::toTwistStamped(vx, vy, wz, stamp, costmap_ros_->getBaseFrameID());
}
return utils::toTwistStamped(vx, wz, stamp, costmap_ros_->getBaseFrameID());
}
void Optimizer::setMotionModel(const std::string & model)
{
if (model == "DiffDrive") {
motion_model_ = std::make_shared<DiffDriveMotionModel>();
} else if (model == "Omni") {
motion_model_ = std::make_shared<OmniMotionModel>();
} else if (model == "Ackermann") {
motion_model_ = std::make_shared<AckermannMotionModel>(parameters_handler_, name_);
} else {
throw std::runtime_error(
std::string(
"Model " + model + " is not valid! Valid options are DiffDrive, Omni, "
"or Ackermann"));
}
}
void Optimizer::setSpeedLimit(double speed_limit, bool percentage)
{
auto & s = settings_;
if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
s.constraints.vx_max = s.base_constraints.vx_max;
s.constraints.vx_min = s.base_constraints.vx_min;
s.constraints.vy = s.base_constraints.vy;
s.constraints.wz = s.base_constraints.wz;
} else {
if (percentage) {
// Speed limit is expressed in % from maximum speed of robot
double ratio = speed_limit / 100.0;
s.constraints.vx_max = s.base_constraints.vx_max * ratio;
s.constraints.vx_min = s.base_constraints.vx_min * ratio;
s.constraints.vy = s.base_constraints.vy * ratio;
s.constraints.wz = s.base_constraints.wz * ratio;
} else {
// Speed limit is expressed in absolute value
double ratio = speed_limit / s.base_constraints.vx_max;
s.constraints.vx_max = s.base_constraints.vx_max * ratio;
s.constraints.vx_min = s.base_constraints.vx_min * ratio;
s.constraints.vy = s.base_constraints.vy * ratio;
s.constraints.wz = s.base_constraints.wz * ratio;
}
}
}
models::Trajectories & Optimizer::getGeneratedTrajectories()
{
return generated_trajectories_;
}
} // namespace mppi