百度apollo planning代码学习-Apollo\modules\planning\math\piecewise_jerk\PiecewiseJerkPathProblem类代码详解

70 篇文章 225 订阅
42 篇文章 7 订阅

概述

PiecewiseJerkPathProblem类是apollo planning模块下modules\planning\math\piecewise_jerk\piecewise_jerk_path_problem.cc/.h实现

从类名来看,应该是PiecewiseJerkPathProblem是分段匀加加速度运动路径问题

从代码来看PiecewiseJerkPathProblem类主要是实现:
对横向路径二次规划优化问题进行构建,主要是构建P矩阵和q矩阵,对横向d,d’,d’‘,d’''以及d偏离dref的距离按照一定的权重计算cost求出最优的路径这个优化问题的构建,但约束并不在该类里设置,而是在调用时通过该类的基类对优化进行设置。

简而言之,该类是路径优化的二次规划问题里的P,q矩阵的构建

*以一个计算示例来说明osqp问题的设置及求解(待上传20220819)

参考链接:
Apollo路径优化
在这里插入图片描述

piecewise_jerk_path_problem.h

#pragma once

#include <utility>
#include <vector>

#include "modules/planning/math/piecewise_jerk/piecewise_jerk_problem.h"

namespace apollo {
namespace planning {

/*
 * @brief:
 * FEM stands for finite element method.FEM代表有限元方法?
 * This class solve an optimization problem:这个类是求解一个优化问题
 * 每一个s对应的横向偏移合起来就是路径path
 * x
 * |
 * |                       P(s1, x1)  P(s2, x2)
 * |            P(s0, x0)                       ... P(s(k-1), x(k-1))
 * |P(start)
 * |
 * |________________________________________________________ s
 *
 * we suppose s(k+1) - s(k) == s(k) - s(k-1)
 * 我们认为相邻离散点的纵向间隔是一样的
 *
 * Given the x, x', x'' at P(start),  The goal is to find x0, x1, ... x(k-1)
 * 给定初始的横向状态d0,d0',d0'',目标就是求解出最优的d0,d1,...dk-1使得整个路径最平滑
 * which makes the line P(start), P0, P(1) ... P(k-1) "smooth".
 */
//公有继承PiecewiseJerkProblem分段加加速度问题类
class PiecewiseJerkPathProblem : public PiecewiseJerkProblem {
 public:
 //分段加加速度路径优化问题类带参构造函数,输入参数离散点数量也是输入的横向边界采样点的数量,delta_s离散点的纵向间隔1.0m,x_init横向的初始状态d0,d0',d0''
  PiecewiseJerkPathProblem(const size_t num_of_knots, const double delta_s,
                           const std::array<double, 3>& x_init);

  virtual ~PiecewiseJerkPathProblem() = default;

 protected:
 //构建二次规划问题的P矩阵,以CSC稀疏矩阵的格式储存到输入的3个指针数组里
 //P_indices P矩阵行索引数组
  //P_indptr P矩阵列索引数组
  //P_data P矩阵上面行列索引对应的元素值的数组
  void CalculateKernel(std::vector<c_float>* P_data,
                       std::vector<c_int>* P_indices,
                       std::vector<c_int>* P_indptr) override;
//构建二次规划问题的q矩阵,构建结果储存到输入的指针q里
  void CalculateOffset(std::vector<c_float>* q) override;
};

}  // namespace planning
}  // namespace apollo

piecewise_jerk_path_problem.cc

#include "modules/planning/math/piecewise_jerk/piecewise_jerk_path_problem.h"

#include "cyber/common/log.h"
#include "modules/planning/common/planning_gflags.h"

namespace apollo {
namespace planning {
//类的带参构造函数
//输入参数:num_of_knots离散点的数量,调用时输入的是横向边界里的元素的数量,横向边界{(d0_lower,d0_upper),(d1_lower,d1_upper)...}
//输入参数:x_init初始的状态(s,s',s''或d,d',d'')
//输入参数:delta_s各个横线边界或者是离散点之间对应的纵向间隔s=1.0m
PiecewiseJerkPathProblem::PiecewiseJerkPathProblem(
    const size_t num_of_knots, const double delta_s,
    const std::array<double, 3>& x_init)
    //这些参数用来初始化另一个类PiecewiseJerkProblem
    : PiecewiseJerkProblem(num_of_knots, delta_s, x_init) {}

//计算内核,又是QP问题,输入又是一个CSC格式的P矩阵,求得的P矩阵按CSC格式存入输入的3个参数中,CSC稀疏矩阵储存,仅储存对角线元素以及其平行的对角元素,省略掉大量的0元素,对于大型矩阵的运算速度大大增加
 //P_indices P矩阵行索引数组
  //P_indptr P矩阵列索引数组
  //P_data P矩阵上面行列索引对应的元素值的数组
void PiecewiseJerkPathProblem::CalculateKernel(std::vector<c_float>* P_data,
                                               std::vector<c_int>* P_indices,
                                               std::vector<c_int>* P_indptr) {
  //将离散点的数量赋值给n
  const int n = static_cast<int>(num_of_knots_);
  //则变量的个数应该就是二次规划QP问题里1/2X^T*P*X+q^T*X变量X的元素的数量,n个离散时间采样点,每个时刻对应的状态有3个(s,s',s''或d,d',d'')
  const int num_of_variables = 3 * n;
  //非零元素的个数 = X的元素的数量 + n - 1 = 4n-1?
  const int num_of_nonzeros = num_of_variables + (n - 1);
  //定义了一个columns来储存P矩阵的各列
  std::vector<std::vector<std::pair<c_int, c_float>>> columns(num_of_variables);
  int value_index = 0;

  // x(i)^2 * (w_x + w_x_ref[i]), w_x_ref might be a uniform value for all x(i)
  // or piecewise values for different x(i)
  //这一项是构建cost函数里对横向偏移量项及避障时导致横向boundary的中心线对参考线的偏移项构成, Wd*∑d^2 + Wdref*∑(d-(dmin+dmax)/2)^2这两项之和把d^2项系数合并就是这个for循环干的事。注:Wdref*∑dmax^2这是一个常数项,求cost函数最小值时可以直接忽略,scale_factor_[0],scale_factor_[1],scale_factor_[2]都是1,weight_x_ref_vec_基本也是个常数项,调用时好像都一样
  //设置P矩阵中
  for (int i = 0; i < n - 1; ++i) {
    columns[i].emplace_back(i, (weight_x_ + weight_x_ref_vec_[i]) /
                                   (scale_factor_[0] * scale_factor_[0]));
    ++value_index;
  }
  // x(n-1)^2 * (w_x + w_x_ref[n-1] + w_end_x)
  // x(n-1)^2 * (w_x + w_x_ref[n-1])这个其实跟上面的for循环干的一件事,只是因为上面的for循环没有到第n项(下标n-1),,但不同的是,这里针对第n个离散横向采样点偏移的平方加了一个终端状态的权重weight_end_state_[0],但是貌似weight_end_state_[0],weight_end_state_[1],weight_end_state_[2]全部为0,直接把第n项横向偏移的平方惩罚项合并到上面那个for循环算了
  columns[n - 1].emplace_back(
      n - 1, (weight_x_ + weight_x_ref_vec_[n - 1] + weight_end_state_[0]) /
                 (scale_factor_[0] * scale_factor_[0]));
  ++value_index;

  // x(i)'^2 * w_dx
  //这个for循环是设置P矩阵中每个离散采样点d'^2的惩罚项的权重,对横向偏移量变化率的权重
  for (int i = 0; i < n - 1; ++i) {
    columns[n + i].emplace_back(
        n + i, weight_dx_ / (scale_factor_[1] * scale_factor_[1]));
    ++value_index;
  }
  // x(n-1)'^2 * (w_dx + w_end_dx) 本来是想对对终端的一阶横向状态单独处理,但是实际上weight_end_state_[1]=0,所以完全终端状态的一阶横向状态可以和上面的for循环合并
  columns[2 * n - 1].emplace_back(2 * n - 1,
                                  (weight_dx_ + weight_end_state_[1]) /
                                      (scale_factor_[1] * scale_factor_[1]));
  ++value_index;

	//横向离散采样点的纵向间隔delta_s_ 通常为1.0m,这里求了个平方是做什么的?
  auto delta_s_square = delta_s_ * delta_s_;
  // x(i)''^2 * (w_ddx + 2 * w_dddx / delta_s^2)
  //这个是对横向状态二阶d''项平方进行惩罚,weight_dddx_ / delta_s_square其实是拆分了一部分加加速度的平方项 加加速度平方项cost=weight_dddx_ ∑(d''(k+1)-d''(k))/delta_s)^2
  columns[2 * n].emplace_back(2 * n,
                              (weight_ddx_ + weight_dddx_ / delta_s_square) /
                                  (scale_factor_[2] * scale_factor_[2]));
  ++value_index;
  for (int i = 1; i < n - 1; ++i) {
    columns[2 * n + i].emplace_back(
        2 * n + i, (weight_ddx_ + 2.0 * weight_dddx_ / delta_s_square) /
                       (scale_factor_[2] * scale_factor_[2]));
    ++value_index;
  }
  //横向加加速度平方项cost(部分)以及横向加速度的平方项cost,其实weight_end_state_[2]=0,注意这里的加速度和加加速度都是横向d对纵向位置s求导
  columns[3 * n - 1].emplace_back(
      3 * n - 1,
      (weight_ddx_ + weight_dddx_ / delta_s_square + weight_end_state_[2]) /
          (scale_factor_[2] * scale_factor_[2]));
  ++value_index;

  // -2 * w_dddx / delta_s^2 * x(i)'' * x(i + 1)''
  //这一块仍然是横向加加速度约束,横向加加速度写成横向加速度的差分形式的平方后,交叉项的权重由这个for循环实现写入P矩阵
  for (int i = 0; i < n - 1; ++i) {
    columns[2 * n + i].emplace_back(2 * n + i + 1,
                                    (-2.0 * weight_dddx_ / delta_s_square) /
                                        (scale_factor_[2] * scale_factor_[2]));
    ++value_index;
  }

  CHECK_EQ(value_index, num_of_nonzeros);

  int ind_p = 0;
  //P_indices P矩阵行索引数组
  //P_indptr P矩阵列索引数组
  //P_data P矩阵上面行列索引对应的元素值的数组
  //将上述写入columns的数据按CSC稀疏矩阵格式储存矩阵P
  for (int i = 0; i < num_of_variables; ++i) {
    P_indptr->push_back(ind_p);
    for (const auto& row_data_pair : columns[i]) {
      P_data->push_back(row_data_pair.second * 2.0);
      P_indices->push_back(row_data_pair.first);
      ++ind_p;
    }
  }
  P_indptr->push_back(ind_p);
}

//Wdref*∑(d-(dmin+dmax)/2)^2计算路径因为避障车道左右边界中心线偏离道路参考线的cost中的交叉项由二次规划的q^T*X项实现,也就是这里的计算offset,由于1/2X^TPX中的1/2为了化成1,所以q^T*X的权重要乘以个2
void PiecewiseJerkPathProblem::CalculateOffset(std::vector<c_float>* q) {
  CHECK_NOTNULL(q);
  const int n = static_cast<int>(num_of_knots_);
  const int kNumParam = 3 * n;
  q->resize(kNumParam, 0.0);

  if (has_x_ref_) {
    for (int i = 0; i < n; ++i) {
      q->at(i) += -2.0 * weight_x_ref_vec_.at(i) * x_ref_[i] / scale_factor_[0];
    }
  }

  if (has_end_state_ref_) {
    q->at(n - 1) +=
        -2.0 * weight_end_state_[0] * end_state_ref_[0] / scale_factor_[0];
    q->at(2 * n - 1) +=
        -2.0 * weight_end_state_[1] * end_state_ref_[1] / scale_factor_[1];
    q->at(3 * n - 1) +=
        -2.0 * weight_end_state_[2] * end_state_ref_[2] / scale_factor_[2];
  }
}

}  // namespace planning
}  // namespace apollo

以一个例子说明P,q矩阵是如何构建的
约束条件呢?

约束条件

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Apollo Planning是一个自动驾驶规划模块,它负责生成自动驾驶车辆的行驶路线和行驶轨迹。该模块的代码主要包括以下几个部分: 1. 地图数据处理:该部分代码主要负责处理地图数据,包括地图的加载、解析和存储等。 2. 车辆状态估计:该部分代码主要负责估计车辆的状态,包括车辆的位置、速度、加速度等。 3. 障碍物检测:该部分代码主要负责检测车辆周围的障碍物,包括车辆前方的障碍物、车辆后方的障碍物等。 4. 路径规划:该部分代码主要负责生成车辆的行驶路线,包括起点、终点、途经点等。 5. 轨迹规划:该部分代码主要负责生成车辆的行驶轨迹,包括车辆的速度、加速度、转向角度等。 总的来说,Apollo Planning代码解读需要对自动驾驶技术有一定的了解,需要熟悉相关的算法和数据结构。同时,还需要对C++编程语言有一定的掌握,能够理解和修改代码。 ### 回答2: Apollo PlanningApollo平台中的一部分,是一种规划算法,用于生成具有速度、加速度、路径跟踪、动态碰撞检测等约束条件的行驶路径。本文将对Apollo Planning中的代码进行解读。 Apollo Planning的核心代码包括两个部分:路径规划器和速度规划器。其中路径规划器的主要任务是在路网中寻找一条从起点到终点的路径,而速度规划器的主要任务则是为规划出的路径生成相应的速度规划和轨迹。 路径规划器中采用的主要算法是基于A*算法的全局规划器和基于Dijkstra算法的局部规划器。全局规划器用于从起点到终点寻找全局路径,而局部规划器则用于在全局路径的基础上进行优化,以生成最终的路径。 在速度规划器中,采用了二次规划、线性插值和基于速度和加速度约束的时间分配等算法,用于根据路网上提供的速度信息和预计的路况等因素生成规划速度和轨迹。 除此之外,还应用了动态碰撞检测算法,用于在行驶过程中实时检测障碍物,并调整行驶路径以避免碰撞。 总之,Apollo Planning代码实现了较为完善的路径规划和速度规划功能,并且综合应用了多种算法和约束条件,使得车辆行驶更加安全、稳定。 ### 回答3: Apollo Planning 代码百度自动驾驶平台 Apollo 中用于路径规划的组件。通过对代码的解读,我们可以了解到路径规划背后的一系列算法和原理。 首先,Apollo Planning 首先需要载入地图信息,以确定行驶的区域和道路网络。这些地图信息包括道路形状、道路宽度、车道数量、速度限制和限制规则等。 然后,Apollo Planning 根据车辆当前位置和目的地位置,通过 A*算法或 Dijkstra 算法等规划出车辆行驶的路径。这一过程中,Apollo Planning 需要考虑各种限制条件,如道路的长度、转弯半径、速度限制、停止标志和交通信号灯等。 接下来,Apollo Planning 将规划出的路径转换为轨迹,以让车辆根据轨迹规划进行动作。这一过程需要考虑车辆的动力学特性,比如加速度、最大速度限制和最大转弯速度等。 在最终生成的行驶轨迹中,需要包含一些基础信息,如轨迹的时间戳、各个点的速度和加速度信息等。这些信息有助于车辆在运行过程中准确地遵守路径规划,并在行驶中做出适时的调整。 总之,Apollo Planning 的核心功能是确定车辆行驶的路线、行驶轨迹和行驶速度等。该组件通过高效的算法和细致的条件考虑,实现自动驾驶车辆的稳定、安全和高效的路径规划。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wujiangzhu_xjtu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值