461 lines
15 KiB
C++
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
|