本篇将介绍速度动态规划优化算法-path time heuristic optimizer(路径时间启发式优化器)
参考planning模块(9)-路径规划(scenario/stage/task) task的执行流程

for (auto task : task_list_) {
      const double start_timestamp = Clock::NowInSeconds();

      ret = task->Execute(frame, &reference_line_info);

modules/planning/planning_interface_base/task_base/common/speed_optimizer.cc

Status SpeedOptimizer::Execute(Frame* frame,
                               ReferenceLineInfo* reference_line_info) {
  Task::Execute(frame, reference_line_info);

  auto ret =
      Process(reference_line_info->path_data(), frame->PlanningStartPoint(),
              reference_line_info->mutable_speed_data());

  RecordDebugInfo(reference_line_info->speed_data());
  return ret;
}

modules/planning/tasks/path_time_heuristic/path_time_heuristic_optimizer.cc

Status PathTimeHeuristicOptimizer::Process(
    const PathData& path_data, const common::TrajectoryPoint& init_point,
    SpeedData* const speed_data)

path_data:是通过AssessPath函数获取到做过有效性检查的路径点数据
init_point:规划起点

path_data.discretized_path().empty()
DiscretizedPath::DiscretizedPath(std::vector<PathPoint> path_points)
    : std::vector<PathPoint>(std::move(path_points)) {}

是上一篇planning模块(12)-路径规划后构建速度规划所用的数据 路径点数据全部转化为笛卡尔坐标系下的数据的路径数据

SearchPathTimeGraph函数

const auto& dp_st_speed_optimizer_config =
      reference_line_info_->IsChangeLanePath()
          ? config_.lane_change_speed_config()
          : config_.default_speed_config();

变道和非变道使用不同的配置参数,配置文件路径:modules/planning/tasks/path_time_heuristic/conf/default_conf.pb.txt

GriddedPathTimeGraph函数

GriddedPathTimeGraph::GriddedPathTimeGraph(
    const StGraphData& st_graph_data, const DpStSpeedOptimizerConfig& dp_config,
    const std::vector<const Obstacle*>& obstacles,
    const common::TrajectoryPoint& init_point)

st_graph_data:存储ST图的所有数据
obstacles:所有障碍物
init_point:规划起点

DpStCost函数

DpStCost::DpStCost(const DpStSpeedOptimizerConfig& config, const double total_t,
                   const double total_s,
                   const std::vector<const Obstacle*>& obstacles,
                   const STDrivableBoundary& st_drivable_boundary,
                   const common::TrajectoryPoint& init_point)

config:速度动态规划优化器的配置
total_t:ST图上的总时间
total_s:ST图上的总纵向距离
st_drivable_boundary:ST可行驶区域边界,用于限制搜索空间
init_point:规划起点

for (const auto& obstacle : obstacles) {
  boundary_map_[obstacle->path_st_boundary().id()] = index++;
}

赋予障碍物ST边界的唯一ID

AddToKeepClearRange(obstacles);

AddToKeepClearRange函数

  for (const auto& obstacle : obstacles) {
    if (obstacle->path_st_boundary().IsEmpty()) {
      continue;
    }
    if (obstacle->path_st_boundary().boundary_type() !=
        STBoundary::BoundaryType::KEEP_CLEAR) {
      continue;
    }
    double start_s = obstacle->path_st_boundary().min_s();
    double end_s = obstacle->path_st_boundary().max_s();
    keep_clear_range_.emplace_back(start_s, end_s);

跳过没有ST边界的障碍物和边界类型不是禁停区的障碍物
当障碍物边界类型是禁停区时,获取障碍物ST边界纵向范围存入keep_clear_range_中

SortAndMergeRange(&keep_clear_range_);
void DpStCost::SortAndMergeRange(
    std::vector<std::pair<double, double>>* keep_clear_range) {
  if (!keep_clear_range || keep_clear_range->empty()) {
    return;
  }
  std::sort(keep_clear_range->begin(), keep_clear_range->end());
  size_t i = 0;
  size_t j = i + 1;
  while (j < keep_clear_range->size()) {
    if (keep_clear_range->at(i).second < keep_clear_range->at(j).first) {
      ++i;
      ++j;
    } else {
      keep_clear_range->at(i).second = std::max(keep_clear_range->at(i).second,
                                                keep_clear_range->at(j).second);
      ++j;
    }
  }
  keep_clear_range->resize(i + 1);
}

如果keep_clear_range中的相邻障碍物ST边界纵向范围存在重叠部分,就会对重叠部分进行合并

  const auto dimension_t =
      static_cast<uint32_t>(std::ceil(total_t / static_cast<double>(unit_t_))) +
      1;

计算时间维度,时间点的数量

d2038381239e417aa43672111619e179.png

  boundary_cost_.resize(obstacles_.size());
  for (auto& vec : boundary_cost_) {
    vec.resize(dimension_t, std::make_pair(-1.0, -1.0));
  }
  accel_cost_.fill(-1.0);
  jerk_cost_.fill(-1.0);

boundary_cost_:缓存每个障碍物在每个时间点的ST边界范围
accel_cost_:存储加速度成本
jerk_cost_:存储加加速度成本
到这DpStCost构造函数就介绍完了

total_length_t_ = st_graph_data_.total_time_by_conf();

total_length_t_:ST图在时间轴上的总长度,7s

unit_t_ = gridded_path_time_graph_config_.unit_t();

unit_t_:时间轴上每个网格的时间间隔,1s

total_length_s_ = st_graph_data_.path_length();

参考线的总长度

dense_unit_s_ = gridded_path_time_graph_config_.dense_unit_s();

dense_unit_s_:密集区域每个网格的纵向大小,车辆附近的轨迹需要高精度
巡航时配置:0.1m
换道时配置:0.25m

sparse_unit_s_ = gridded_path_time_graph_config_.sparse_unit_s();

sparse_unit_s_:稀疏区域每个网格的纵向大小,远距离规划不需要太高精度
巡航时配置:1.0m
换道时配置:1.0m

dense_dimension_s_ = gridded_path_time_graph_config_.dense_dimension_s();

dense_dimension_s_:纵向距离,密集区离散状态点的数量
巡航时配置:101个
换道时配置:21个

max_acceleration_ =
    std::min(std::abs(vehicle_param_.max_acceleration()),
             std::abs(gridded_path_time_graph_config_.max_acceleration()));
max_deceleration_ =
    -1.0 *
    std::min(std::abs(vehicle_param_.max_deceleration()),
             std::abs(gridded_path_time_graph_config_.max_deceleration()));

DP-ST图搜索中的加速度和减速度约束 ,确保规划出的速度轨迹符合车辆动力学特性和乘坐舒适性要求

max_acceleration_ =
     std::min(std::abs(vehicle_param_.max_acceleration()),
              std::abs(gridded_path_time_graph_config_.max_acceleration()));
 max_deceleration_ =
     -1.0 *
     std::min(std::abs(vehicle_param_.max_deceleration()),
              std::abs(gridded_path_time_graph_config_.max_deceleration()));

vehicle_param_.max_acceleration():车辆动力系统的最大加速能力,​2m/s^2
gridded_path_time_graph_config_.max_acceleration():规划时采用的保守约束,​2m/s^2

vehicle_param_.max_deceleration():车辆动力系统的最大减速能力,​-6m/s^2
gridded_path_time_graph_config_.max_acceleration():规划时采用的保守约束,​-4.0m/s^2

在车辆能力和规划约束之间选择更保守的值

到这GriddedPathTimeGraph函数就介绍完了

st_graph.Search(speed_data).ok()

调用动态规划算法在ST图上搜索最优路径,生成速度规划结果

Status GriddedPathTimeGraph::Search(SpeedData* const speed_data) {
for (const auto& boundary : st_graph_data_.st_boundaries()) {
    // KeepClear obstacles not considered in Dp St decision
    if (boundary->boundary_type() == STBoundary::BoundaryType::KEEP_CLEAR) {
      continue;
    }

遍历所有障碍物ST边界跳过ST边界类型是禁停区的障碍物

if (boundary->IsPointInBoundary({0.0, 0.0}) ||
      (std::fabs(boundary->min_t()) < kBounadryEpsilon &&
       std::fabs(boundary->min_s()) < kBounadryEpsilon))

IsPointInBoundary函数

bool STBoundary::IsPointInBoundary(const STPoint& st_point) const {
  if (st_point.t() <= min_t_ || st_point.t() >= max_t_) {
    return false;
  }
  size_t left = 0;
  size_t right = 0;

st_point:ST图的初始点(s=0.0, t=0.0)
IsPointInBoundary:检查点是否在障碍物ST边界内部
遍历所有障碍物的ST边界,然后判断ST图的初始点是否在障碍物的ST的边界内

GetIndexRange函数

bool STBoundary::GetIndexRange(const std::vector<STPoint>& points,
                               const double t, size_t* left,
                               size_t* right) const {
  CHECK_NOTNULL(left);
  CHECK_NOTNULL(right);
  if (t < points.front().t() || t > points.back().t()) {
    AERROR << "t is out of range. t = " << t;
    return false;
  }

points:是当前障碍物的ST边界的下边界点集

auto comp = [](const STPoint& p, const double t) { return p.t() < t; };
  auto first_ge = std::lower_bound(points.begin(), points.end(), t, comp);
  size_t index = std::distance(points.begin(), first_ge);
  if (index == 0) {
    *left = *right = 0;
  } else if (first_ge == points.end()) {
    *left = *right = points.size() - 1;
  } else {
    *left = index - 1;
    *right = index;
  }

在当前障碍物的ST边界的下边界点中,找到第一个大于等于ST图初始点时间的下边界点,然后获取到这个点在下边界点中的索引

如果索引index = 0,则让left = right = 0进行返回
如果在当前障碍物的ST边界的下边界中,没有找到大于等于ST图初始点时间的下边界点,则让 *left = *right = points.size() - 1进行返回
如果找到了第一个大于等于ST图初始点时间的下边界点,则令left = index - 1, right = index
right:是找到的第一个大于等于ST图初始点时间的下边界点的索引
left:是找到的这个下边界点的前一个点的索引

由于在apollo的代码里point_extension的配置默认是0,所以构建的障碍物的下边界和上边界的点集中S值都是正数

31414eeef6a34b54b6b33bca1c26d205.png

1dfb6af1b68c4a7e8455ac597abc4bb0.png

5e8e87e4b05f4defb8e9dcb72887abac.png

这就意味着ST图的初始点与从上边界点/下边界点获取到的left和right索引对应的边界点所构成的向量的叉积一直为0,也就意味着不会出现ST图初始点在障碍物ST边界范围内的情况

  const double check_upper = common::math::CrossProd(
      st_point, upper_points_[left], upper_points_[right]);
  const double check_lower = common::math::CrossProd(
      st_point, lower_points_[left], lower_points_[right]);

  return (check_upper * check_lower < 0);

也就意味着IsPointInBoundary函数不会返回true
可以先这么理解,如果在实际场景中发现理解错误再进行修改

std::fabs(boundary->min_t()) < kBounadryEpsilon &&
std::fabs(boundary->min_s()) < kBounadryEpsilon)

当障碍物中边界点中最小的纵向距离和时间很小时(ST初始点与障碍物很近时)

 dimension_t_ = static_cast<uint32_t>(std::ceil(
                    total_length_t_ / static_cast<double>(unit_t_))) +
                1;
 std::vector<SpeedPoint> speed_profile;
 double t = 0.0;
 for (uint32_t i = 0; i < dimension_t_; ++i, t += unit_t_) {
   speed_profile.push_back(PointFactory::ToSpeedPoint(0, t));
 }
 *speed_data = SpeedData(speed_profile);

unit_t_:是一个单位时间的步长,1.0s
total_length_t_:一次(一帧)规划的总时间,7.0s
dimension_t_:时间维度,时间索引点的数量,​\left \lceil \frac{7}{1}+ 1\right \rceil=8

fa6e74db1ebc4475988d2b91eed87319.png

按照单位时间1.0s(也就是遍历的步长),遍历时间索引点的数量,初始化SpeedPoint结构

  static inline SpeedPoint ToSpeedPoint(const double s, const double t,
                                        const double v = 0, const double a = 0,
                                        const double da = 0) {
    SpeedPoint speed_point;
    speed_point.set_s(s);
    speed_point.set_t(t);
    speed_point.set_v(v);
    speed_point.set_a(a);
    speed_point.set_da(da);
    return speed_point;
  }

s:是纵向距离
t:是当前时间
v:是当前时间点的瞬时速率
a:当前时间加速度
da:当前时间的加加速度(jerk)

*speed_data = SpeedData(speed_profile);

构建参考线信息中的speed_data,作为障碍物与ST图初始点很近时的默认fallback轨迹速度规划数据
此时,速度,加速度,加加速度全都为0也就意味着车辆是停止状态

  if (!InitCostTable().ok()) {
    const std::string msg = "Initialize cost table failed.";
    AERROR << msg;
    return Status(ErrorCode::PLANNING_ERROR, msg);
  }

InitCostTable函数

巡航时
dense_dimension_s_:密集区纵向距离离散状态点的数量101
dense_dimension_s_ - 1:密集区网格数量,100
dense_unit_s_:密集区域网格单位长度0.1m
static_cast(dense_dimension_s_ - 1) * dense_unit_s_:密集区域纵向长度:100 * 0.1 =10m

 double sparse_length_s =
      total_length_s_ -
      static_cast<double>(dense_dimension_s_ - 1) * dense_unit_s_;

total_length_s_:路径规划出来的基于笛卡尔坐标系的离散后的规划路线长度

sparse_dimension_s_ =
 sparse_length_s > std::numeric_limits<double>::epsilon()
     ? static_cast<uint32_t>(std::ceil(sparse_length_s / sparse_unit_s_))
     : 0;

sparse_length_s:稀疏区域范围纵向距离长度
sparse_unit_s_:稀疏区域网格单位长度
sparse_dimension_s_ = std::ceil(sparse_length_s / sparse_unit_s_):稀疏区域网格数量

dimension_s_ = dense_dimension_s_ + sparse_dimension_s_;

dimension_s_:ST图表示纵向距离的网格数量

cost_table_ = std::vector<std::vector<StGraphPoint>>(
      dimension_t_, std::vector<StGraphPoint>(dimension_s_, StGraphPoint()));

构建dimension_t_行,dimension_s_列的cost_table_

 double curr_t = 0.0;
  for (uint32_t i = 0; i < cost_table_.size(); ++i, curr_t += unit_t_) {
    auto& cost_table_i = cost_table_[i];
    double curr_s = 0.0;
    for (uint32_t j = 0; j < dense_dimension_s_; ++j, curr_s += dense_unit_s_) {
      cost_table_i[j].Init(i, j, STPoint(curr_s, curr_t));
      debug.AddPoint("dp_node_points", curr_t, curr_s);
    }

初始化密集区ST图代价表

    curr_s = static_cast<double>(dense_dimension_s_ - 1) * dense_unit_s_ +
             sparse_unit_s_;
    for (uint32_t j = dense_dimension_s_; j < cost_table_i.size();
         ++j, curr_s += sparse_unit_s_) {
      cost_table_i[j].Init(i, j, STPoint(curr_s, curr_t));
      debug.AddPoint("dp_node_points", curr_t, curr_s);
    }

初始化稀疏区ST图代价表

eaf0a036340f4140b4ca850357fcf8b1.png

  const auto& cost_table_0 = cost_table_[0];
  spatial_distance_by_index_ = std::vector<double>(cost_table_0.size(), 0.0);
  for (uint32_t i = 0; i < cost_table_0.size(); ++i) {
    spatial_distance_by_index_[i] = cost_table_0[i].point().s();
  }

spatial_distance_by_index_:行号到距离的映射数组
InitCostTable函数就介绍完了

InitSpeedLimitLookUp函数

 for (uint32_t i = 0; i < dimension_s_; ++i) {
    speed_limit_by_index_[i] =
        speed_limit.GetSpeedLimitByS(cost_table_[0][i].point().s());
  }

获取(0, s)位置的限速值

GetSpeedLimitByS函数

Status GriddedPathTimeGraph::InitSpeedLimitLookUp() {
  speed_limit_by_index_.clear();

  speed_limit_by_index_.resize(dimension_s_);
  const auto& speed_limit = st_graph_data_.speed_limit();

speed_limit:赋值可参考上一篇planning模块(14) task SPEED_BOUNDS_PRIORI_DECIDER LoadData函数

  for (uint32_t i = 0; i < dimension_s_; ++i) 
    speed_limit_by_index_[i] =
        speed_limit.GetSpeedLimitByS(cost_table_[0][i].point().s());
  }

GetSpeedLimitByS函数

double SpeedLimit::GetSpeedLimitByS(const double s) const {
  CHECK_GE(speed_limit_points_.size(), 2U);
  DCHECK_GE(s, speed_limit_points_.front().first);

  auto compare_s = [](const std::pair<double, double>& point, const double s) {
    return point.first < s;
  };

  auto it_lower = std::lower_bound(speed_limit_points_.begin(),
                                   speed_limit_points_.end(), s, compare_s);

  if (it_lower == speed_limit_points_.end()) {
    return (it_lower - 1)->second;
  }
  return it_lower->second;
}

获取cost_table_代价表中(0, s)点的速度限制

CalculateTotalCost函数

CalculateTotalCost 第一性原理

本质问题

代码要解决什么物理问题?

自车从 (s=0, t=0) 出发,找一条最优的速度曲线。

怎么表示速度曲线?

在 ST 图上选一系列点:(0,0) → (c1,r1) → (c2,r2) → ... → (c_end, r_end)

什么叫最优?

这些点的 total_cost() 之和最小。


代码的本质(就一个公式)

// 第 429-440 行(c=2 的情况)
const double cost = cost_cr.obstacle_cost()
                  + cost_cr.spatial_potential_cost()
                  + pre_col[r_pre].total_cost()  // 前驱的成本
                  + CalculateEdgeCostForThirdCol(...);  // 从前驱走过来的成本

if (cost < cost_cr.total_cost()) {
  cost_cr.SetTotalCost(cost);  // 更新为更小的值
  cost_cr.SetPrePoint(pre_col[r_pre]);  // 记录从哪来
}

翻译成人话

到当前点的成本 = 前驱的成本 + 从前驱走过来的成本

选哪个前驱?选让总成本最小的那个!

size_t next_highest_row = 0;
size_t next_lowest_row = 0;

[next_highest_row, next_lowest_row]:要遍历行数的范围

for (size_t c = 0; c < cost_table_.size(); ++c) {

最外层循环,遍历时间(T)维度

for (size_t r = next_lowest_row; r <= next_highest_row; ++r)

然后遍历纵向距离(S)维度
cost_table_代价表中时间维度表示列,距离维度表示行

for (size_t r = next_lowest_row; r <= next_highest_row; ++r) {
      const auto& cost_cr = cost_table_[c][r];
      if (cost_cr.total_cost() < std::numeric_limits<double>::infinity()) {
        size_t h_r = 0;
        size_t l_r = 0;
        GetRowRange(cost_cr, &h_r, &l_r);
        highest_row = std::max(highest_row, h_r);
        lowest_row = std::min(lowest_row, l_r);
      }
    }
next_highest_row = highest_row;
next_lowest_row = lowest_row;

每次遍历完纵向距离维度(行)后,重新获取下一次要遍历的纵向距离维度(行)的范围


动态规划完整推导流程

网格配置
时间维度(4 列):

  • unit_t_ = 1.0 秒
  • dimension_t_ = 4 列(t = 0, 1, 2, 3 秒)
  • 列号:c = 0, 1, 2, 3

空间维度(3 行):

  • unit_s_ = 3.0 米
  • dimension_s_ = 3 行(r = 0, 1, 2)
  • spatial_distance_by_index_ = [0.0, 3.0, 6.0] 米

初始状态:

  • init_point_.v() = 3.0 m/s
  • init_point_.a() = 0.0 m/s²
  • init_point_.s() = 0.0 m
  • init_point_.t() = 0.0 s

约束条件:

  • max_acceleration_ = 1.5 m/s²
  • max_deceleration_ = -1.5 m/s²
  • FLAGS_planning_upper_speed_limit = 10.0 m/s
  • kSpeedRangeBuffer = 0.20
  • min_s_consider_speed = unit_s_ * 7 = 21.0 米

配置参数(简化):

  • config_.default_speed_cost() = 1.0
  • config_.accel_penalty() = 1.0
  • config_.decel_penalty() = 1.0
  • config_.positive_jerk_coeff() = 0.5
  • config_.negative_jerk_coeff() = 0.5
  1. obstacle_cost = 0.0(无障碍)
  2. spatial_potential_cost = 0.0(简化)
  3. speed_cost = 0.0(假设速度合理,不超速也不低速)
  4. accel_cost = 1.0 * accel²(加速度平方,系数为 1)
  5. jerk_cost = 0.5 * jerk²(加加速度平方,系数为 0.5)

为了了解推导流程,简化代价计算

推导流程
初始化 cost_table_

所有点初始化为:
total_cost() = infinity
optimal_speed() = 0.0
pre_point() = nullptr

处理起点 (c=0, r=0)

调用:CalculateCostAt(msg={c: 0, r: 0})

c = 0, r = 0
cost_cr = cost_table_[0][0]

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 2. c == 0,处理起点
cost_cr.SetTotalCost(0.0)
cost_cr.SetOptimalSpeed(init_point_.v()) = 3.0 m/s
return

结果

cost_table_[0][0]:
point: s=0.0m, t=0.0s
total_cost() = 0.0
optimal_speed() = 3.0 m/s
pre_point() = nullptr

处理第二列 (c=1)
计算 (c=1, r=0)

调用:CalculateCostAt(msg={c: 1, r: 0})

c = 1, r = 0
cost_cr = cost_table_[1][0]
cost_init = cost_table_[0][0]

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 3. c == 1
// 计算加速度
point.s() = 0 * 3.0 = 0.0 米
acc = 2 * (0.0 / 1.0 - 3.0) / 1.0
    = 2 * (-3.0)
    = -6.0 m/s²

// 检查加速度约束
if (acc < max_deceleration_ || acc > max_acceleration_)
   (-6.0 < -1.5 || -6.0 > 1.5) ? 是!
→ return(减速度过大)

结果

cost_table_[1][0]:
total_cost() = infinity(不可达)

计算 (c=1, r=1)

调用:CalculateCostAt(msg={c: 1, r: 1})

c = 1, r = 1
cost_cr = cost_table_[1][1]
cost_init = cost_table_[0][0]

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 3. c == 1
// 计算加速度
point.s() = 1 * 3.0 = 3.0 米
acc = 2 * (3.0 / 1.0 - 3.0) / 1.0
    = 2 * (3.0 - 3.0)
    = 0.0 m/s²

// 检查加速度约束
if (0.0 < -1.5 || 0.0 > 1.5) ? 否 ✓

// 检查速度
init_point_.v() + acc * unit_t_ = 3.0 + 0.0 * 1.0 = 3.0 m/s
if (3.0 < -kDoubleEpsilon && 3.0 > 21.0) ? 否 ✓

// 检查碰撞 ✓

// 计算总成本
// CalculateEdgeCostForSecondCol = speed_cost + accel_cost + jerk_cost
// speed_cost = 0.0(速度合理)
// accel_cost = 1.0 * accel² = 1.0 * 0.0² = 0.0
// jerk_cost = 0.5 * jerk² = 0.5 * 0.0² = 0.0

cost_cr.SetTotalCost(
    0.0 + 0.0 + cost_init.total_cost() + 0.0)
  = 0.0 + 0.0 + 0.0 + 0.0 = 0.0

cost_cr.SetPrePoint(cost_init) = cost_table_[0][0]
cost_cr.SetOptimalSpeed(init_point_.v() + acc * unit_t_)
  = 3.0 + 0.0 * 1.0 = 3.0 m/s

结果

cost_table_[1][1]:
point: s=3.0m, t=1.0s
total_cost() = 0.0
optimal_speed() = 3.0 m/s
pre_point() = cost_table_[0][0]

计算 (c=1, r=2)

调用:CalculateCostAt(msg={c: 1, r: 2})

c = 1, r = 2
cost_cr = cost_table_[1][2]

// 计算加速度
point.s() = 2 * 3.0 = 6.0 米
acc = 2 * (6.0 / 1.0 - 3.0) / 1.0
    = 2 * (6.0 - 3.0)
    = 6.0 m/s²

// 检查加速度约束
if (6.0 < -1.5 || 6.0 > 1.5) ? 是!
→ return(加速度过大)

结果

cost_table_[1][2]:
total_cost() = infinity(不可达)

处理第三列 (c=2)
计算 (c=2, r=0)

调用:CalculateCostAt(msg={c: 2, r: 0})

c = 2, r = 0
cost_cr = cost_table_[2][0]
point: s=0.0m, t=2.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围(详细步骤)
// 4.1 计算前一列的最低可达位置
// 物理含义:从当前点往回看,在 1 秒内以最大速度能到达的最远位置
pre_lowest_s = cost_cr.point().s() - 
               FLAGS_planning_upper_speed_limit * (1 + kSpeedRangeBuffer) * unit_t_
             = 0.0 - 10.0 * (1 + 0.20) * 1.0
             = 0.0 - 12.0
             = -12.0 米

// 4.2 在 spatial_distance_by_index_ 中查找 >= pre_lowest_s 的最小位置
// spatial_distance_by_index_ = [0.0, 3.0, 6.0]
// 含义:每行对应的累积距离
pre_lowest_itr = std::lower_bound([0.0, 3.0, 6.0].begin(), 
                                  [0.0, 3.0, 6.0].end(), 
                                  -12.0)
               // lower_bound 返回第一个 >= -12.0 的迭代器
               // 0.0 >= -12.0,所以返回指向 0.0 的迭代器
               = 指向 [0.0] 的迭代器

// 4.3 计算 r_low(搜索范围的最小行号)
if (pre_lowest_itr == spatial_distance_by_index_.end()) {
  // 如果所有位置都 < pre_lowest_s,则 r_low = 最大行号
  r_low = dimension_s_ - 1 = 2
} else {
  // 否则计算迭代器的距离(索引)
  r_low = std::distance([0.0, 3.0, 6.0].begin(), 指向 [0.0] 的迭代器)
        = 0  // 第一个元素的索引是 0
}

// 4.4 计算搜索范围内的前驱点数量
// 当前行号 r = 0,搜索范围是 [r_low, r] = [0, 0]
r_pre_size = r - r_low + 1
           = 0 - 0 + 1
           = 1  // 只有 1 个前驱点需要检查

// 4.5 获取前一列的引用
pre_col = cost_table_[c - 1] = cost_table_[1]
// pre_col[0] = cost_table_[1][0]
// pre_col[1] = cost_table_[1][1]
// pre_col[2] = cost_table_[1][2]

// 4.6 初始化当前速度限制
curr_speed_limit = speed_limit = speed_limit_by_index_[0]

// 5. c == 2,遍历前驱(详细步骤)
// 循环次数:r_pre_size = 1
for (i = 0; i < 1; ++i) {
  // 5.1 计算前驱行号
  // i=0 时,r_pre = r - 0 = 0(检查第 0 行)
  // i=1 时,r_pre = r - 1(检查第 r-1 行)
  // 从当前行往下遍历,检查所有可能的前驱
  r_pre = r - i = 0 - 0 = 0
  
  // 5.2 检查前驱点是否可达
  // 条件 1:前驱点的 total_cost 不能是 infinity
  // 条件 2:前驱点必须有 pre_point(不是起点)
  if (std::isinf(pre_col[0].total_cost()) || 
      pre_col[0].pre_point() == nullptr) {
     // pre_col[0] = cost_table_[1][0]
     // cost_table_[1][0].total_cost() = infinity(之前计算的)
     // infinity == infinity ? 是!
     → continue(跳过这个前驱)
  }
  
  // 如果循环结束都没有找到合法前驱,则当前点也不可达
}

// 最终结果:没有合法前驱,cost_table_[2][0] 保持 infinity

结果

cost_table_[2][0]:
total_cost() = infinity(没有合法前驱)

计算 (c=2, r=1)

调用:CalculateCostAt(msg={c: 2, r: 1})

c = 2, r = 1
cost_cr = cost_table_[2][1]
point: s=3.0m, t=2.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围
// 4.1 计算 pre_lowest_s
pre_lowest_s = cost_cr.point().s() - 
               FLAGS_planning_upper_speed_limit * (1 + kSpeedRangeBuffer) * unit_t_
             = 3.0 - 10.0 * 1.20 * 1.0
             = 3.0 - 12.0
             = -9.0 米

// 4.2 查找 r_low
pre_lowest_itr = std::lower_bound([0.0, 3.0, 6.0].begin(), 
                                  [0.0, 3.0, 6.0].end(), 
                                  -9.0)
               // 0.0 >= -9.0,返回指向 [0.0] 的迭代器
               = 指向 [0.0] 的迭代器

// 4.3 计算 r_low
r_low = std::distance([0.0, 3.0, 6.0].begin(), 指向 [0.0] 的迭代器)
      = 0

// 4.4 计算搜索范围大小
// 当前行号 r = 1,搜索范围是 [r_low, r] = [0, 1]
r_pre_size = r - r_low + 1
           = 1 - 0 + 1
           = 2  // 需要检查 2 个前驱点:r_pre = 1 和 r_pre = 0
                // - r_pre=1: cost_table_[1][1] (t=1.0s, s=3.0m)
                // - r_pre=0: cost_table_[1][0] (t=1.0s, s=0.0m)

// 4.5 获取前一列
pre_col = cost_table_[1]

// 4.6 初始化速度限制
curr_speed_limit = speed_limit_by_index_[1]

// 5. c == 2,遍历前驱
// 循环次数:r_pre_size = 2
for (i = 0; i < 2; ++i) {
  // ========== 第 1 次循环:i=0 ==========
  // 5.1 计算前驱行号
  r_pre = r - i = 1 - 0 = 1
  // 前驱点坐标:(t=1.0s, s=3.0m) ← cost_table_[1][1]
  
  // 5.2 检查前驱点是否可达
  if (std::isinf(pre_col[1].total_cost()) || 
      pre_col[1].pre_point() == nullptr) {
     // pre_col[1] = cost_table_[1][1]
     // cost_table_[1][1].total_cost() = 0.0 ✓
     // cost_table_[1][1].pre_point() = cost_table_[0][0] ✓
     → 条件不满足,继续执行
  }
  
  // 5.3 计算加速度
  // 公式:curr_a = 2 * ((当前 s - 前驱 s) / unit_t - 前驱速度) / unit_t
  curr_a = 2 * ((cost_cr.point().s() - pre_col[1].point().s()) / unit_t_ -
                pre_col[1].GetOptimalSpeed()) / unit_t_
         = 2 * ((3.0 - 3.0) / 1.0 - 3.0) / 1.0
         = 2 * (0.0 - 3.0)
         = 2 * (-3.0)
         = -6.0 m/s²
  
  // 5.4 检查加速度约束
  if (curr_a < max_deceleration_ || curr_a > max_acceleration_) {
     // -6.0 < -1.5 ? 是!
     → continue(减速度过大,跳过)
  }
  
  // ========== 第 2 次循环:i=1 ==========
  // 5.1 计算前驱行号
  r_pre = r - i = 1 - 1 = 0
  // 前驱点坐标:(t=1.0s, s=0.0m) ← cost_table_[1][0]
  
  // 5.2 检查前驱点是否可达
  if (std::isinf(pre_col[0].total_cost()) || 
      pre_col[0].pre_point() == nullptr) {
     // pre_col[0] = cost_table_[1][0]
     // cost_table_[1][0].total_cost() = infinity ✗
     → 条件满足,continue(跳过)
  }
}

// 最终结果:两个前驱都不合法,cost_table_[2][1] 保持 infinity

结果

cost_table_[2][1]:
total_cost() = infinity

计算 (c=2, r=2)

调用:CalculateCostAt(msg={c: 2, r: 2})

c = 2, r = 2
cost_cr = cost_table_[2][2]
point: s=6.0m, t=2.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围
// 4.1 计算 pre_lowest_s
pre_lowest_s = 6.0 - 10.0 * 1.20 * 1.0
             = 6.0 - 12.0
             = -6.0 米

// 4.2 查找 r_low
pre_lowest_itr = std::lower_bound([0.0, 3.0, 6.0].begin(), 
                                  [0.0, 3.0, 6.0].end(), 
                                  -6.0)
               // 0.0 >= -6.0,返回指向 [0.0] 的迭代器

// 4.3 计算 r_low
r_low = 0

// 4.4 计算搜索范围大小
// 当前行号 r = 2,搜索范围是 [r_low, r] = [0, 2]
// 
// **为什么搜索范围是 [r_low, r] 而不是 [0, dimension_s_-1]?**
// 1. 上限是 r(当前行):
//    - 因为车辆不能"倒车",s 不能减小
//    - 前驱点的 s 必须 <= 当前点的 s
//    - 所以只需要检查行号 <= r 的点
// 
// 2. 下限是 r_low:
//    - r_low 是根据 pre_lowest_s 计算出来的
//    - s < pre_lowest_s 的点,车辆无法在 1 秒内到达
//    - 这些点被剪枝掉,不需要检查
// 
// 这就是为什么只检查"部分"前驱点,而不是所有点!
r_pre_size = r - r_low + 1
           = 2 - 0 + 1
           = 3  // 需要检查 3 个前驱点:r_pre = 2, 1, 0
                // - r_pre=2: cost_table_[1][2] (t=1.0s, s=6.0m)
                // - r_pre=1: cost_table_[1][1] (t=1.0s, s=3.0m)
                // - r_pre=0: cost_table_[1][0] (t=1.0s, s=0.0m)

// 4.5 获取前一列
pre_col = cost_table_[1]

// 4.6 初始化速度限制
curr_speed_limit = speed_limit_by_index_[2]

// 5. c == 2,遍历前驱
// 循环次数:r_pre_size = 3
for (i = 0; i < 3; ++i) {
  // ========== 第 1 次循环:i=0 ==========
  // 5.1 计算前驱行号
  r_pre = r - i = 2 - 0 = 2
  // 前驱点坐标:(t=1.0s, s=6.0m) ← cost_table_[1][2]
  
  // 5.2 检查前驱点是否可达
  if (std::isinf(pre_col[2].total_cost()) || 
      pre_col[2].pre_point() == nullptr) {
     // pre_col[2] = cost_table_[1][2]
     // cost_table_[1][2].total_cost() = infinity ✗
     → continue(跳过)
  }
  
  // ========== 第 2 次循环:i=1 ==========
  // 5.1 计算前驱行号
  r_pre = r - i = 2 - 1 = 1
  // 前驱点坐标:(t=1.0s, s=3.0m) ← cost_table_[1][1]
  
  // 5.2 检查前驱点是否可达
  if (std::isinf(pre_col[1].total_cost()) || 
      pre_col[1].pre_point() == nullptr) {
     // pre_col[1] = cost_table_[1][1]
     // cost_table_[1][1].total_cost() = 0.0 ✓
     // cost_table_[1][1].pre_point() = cost_table_[0][0] ✓
     → 条件不满足,继续执行
  }
  
  // 5.3 计算加速度
  curr_a = 2 * ((cost_cr.point().s() - pre_col[1].point().s()) / unit_t_ -
                pre_col[1].GetOptimalSpeed()) / unit_t_
         = 2 * ((6.0 - 3.0) / 1.0 - 3.0) / 1.0
         = 2 * (3.0 - 3.0)
         = 0.0 m/s²
  
  // 5.4 检查加速度约束
  if (curr_a < max_deceleration_ || curr_a > max_acceleration_) {
     // 0.0 < -1.5 ? 否
     // 0.0 > 1.5 ? 否
     → 条件不满足,继续执行
  }
  
  // 5.5 检查速度合理性
  if (pre_col[1].GetOptimalSpeed() + curr_a * unit_t_ < -kDoubleEpsilon &&
      cost_cr.point().s() > min_s_consider_speed) {
     // 3.0 + 0.0 * 1.0 = 3.0
     // 3.0 < -kDoubleEpsilon ? 否
     → 条件不满足,继续执行
  }
  
  // 5.6 检查碰撞
  if (CheckOverlapOnDpStGraph(st_graph_data_.st_boundaries(), 
                              cost_cr, pre_col[1])) {
     // 假设无碰撞
     → 返回 false,继续执行
  }
  
  // 5.7 更新当前速度限制
  // 取当前行和前驱行的速度限制的最小值
  curr_speed_limit = std::fmin(curr_speed_limit, 
                               speed_limit_by_index_[r_pre])
                   = std::fmin(speed_limit_by_index_[2], 
                               speed_limit_by_index_[1])
  
  // 5.8 计算成本
  // CalculateEdgeCostForThirdCol = speed_cost + accel_cost + jerk_cost
  // first = cost_table_[0][0].point = (0.0, 0.0)
  // second = pre_col[1].point = (3.0, 1.0)
  // third = cost_cr.point = (6.0, 2.0)
  
  // speed_cost = 0.0(假设速度合理)
  // accel = (first.s + third.s - 2*second.s) / unit_t²
  //       = (0.0 + 6.0 - 2*3.0) / 1.0²
  //       = 0.0 m/s²
  // accel_cost = 1.0 * 0.0² = 0.0
  // jerk_cost = 0.0(c=2 时不计算 jerk)
  
  edge_cost = 0.0 + 0.0 + 0.0 = 0.0
  
  cost = cost_cr.obstacle_cost() +
         cost_cr.spatial_potential_cost() +
         pre_col[1].total_cost() +
         edge_cost
       = 0.0 + 0.0 + 0.0 + 0.0
       = 0.0
  
  // 5.9 更新最优解
  if (cost < cost_cr.total_cost()) {
     // 0.0 < infinity ? 是!
     cost_cr.SetTotalCost(0.0)
     cost_cr.SetPrePoint(pre_col[1])  // 记录前驱为 [1][1]
     cost_cr.SetOptimalSpeed(pre_col[1].GetOptimalSpeed() + curr_a * unit_t_)
       = 3.0 + 0.0 * 1.0 = 3.0 m/s
  }
  
  // ========== 第 3 次循环:i=2 ==========
  // 5.1 计算前驱行号
  r_pre = r - i = 2 - 2 = 0
  // 前驱点坐标:(t=1.0s, s=0.0m) ← cost_table_[1][0]
  
  // 5.2 检查前驱点是否可达
  if (std::isinf(pre_col[0].total_cost()) || ...) {
     // pre_col[0] = cost_table_[1][0]
     // cost_table_[1][0].total_cost() = infinity ✗
     → continue(跳过)
  }
}

// 最终结果:找到唯一合法前驱 [1][1],更新 cost_table_[2][2]

结果

cost_table_[2][2]:
point: s=6.0m, t=2.0s
total_cost() = 0.0
optimal_speed() = 3.0 m/s
pre_point() = cost_table_[1][1]

处理第四列 (c=3)
计算 (c=3, r=0)

调用:CalculateCostAt(msg={c: 3, r: 0})

c = 3, r = 0
cost_cr = cost_table_[3][0]
point: s=0.0m, t=3.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围
pre_lowest_s = 0.0 - 12.0 = -12.0
r_low = 0
r_pre_size = 0 - 0 + 1 = 1
pre_col = cost_table_[2]

// 遍历前驱 r_pre = 0
for (i = 0; i < 1; ++i) {
  r_pre = 0
  
  // 检查前驱点
  if (std::isinf(pre_col[0].total_cost()) || pre_col[0].pre_point() == nullptr)
     pre_col[0] = cost_table_[2][0]
     total_cost() = infinity ✗
  → continue
}

结果

cost_table_[3][0]:
 total_cost() = infinity

计算 (c=3, r=1)

调用:CalculateCostAt(msg={c: 3, r: 1})

c = 3, r = 1
cost_cr = cost_table_[3][1]
point: s=3.0m, t=3.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围
pre_lowest_s = 3.0 - 12.0 = -9.0
r_low = 0
r_pre_size = 1 - 0 + 1 = 2
pre_col = cost_table_[2]

// 遍历前驱 r_pre = 1, 0
for (i = 0; i < 2; ++i) {
  // i=0: r_pre = 1
  if (std::isinf(pre_col[1].total_cost()) || pre_col[1].pre_point() == nullptr)
     pre_col[1] = cost_table_[2][1]
     total_cost() = infinity ✗
  → continue
  
  // i=1: r_pre = 0
  if (std::isinf(pre_col[0].total_cost()) || ...)
     → continue
}

结果

cost_table_[3][1]:
 total_cost() = infinity

计算 (c=3, r=2)

调用:CalculateCostAt(msg={c: 3, r: 2})

c = 3, r = 2
cost_cr = cost_table_[3][2]
point: s=6.0m, t=3.0s

// 1. 初始化成本
cost_cr.SetObstacleCost(0.0)
cost_cr.SetSpatialPotentialCost(0.0)

// 4. 计算搜索范围
pre_lowest_s = 6.0 - 12.0 = -6.0
r_low = 0
r_pre_size = 2 - 0 + 1 = 3
pre_col = cost_table_[2]

// 遍历前驱 r_pre = 2, 1, 0
for (i = 0; i < 3; ++i) {
  // i=0: r_pre = 2
  if (std::isinf(pre_col[2].total_cost()) || pre_col[2].pre_point() == nullptr)
     pre_col[2] = cost_table_[2][2]
     total_cost() = 0.0 ✓
     pre_point() = cost_table_[1][1] ✓
  → 继续
  
  // 计算加速度
  curr_a = 2 * ((6.0 - 6.0) / 1.0 - 3.0) / 1.0
         = 2 * (0.0 - 3.0) = -6.0 m/s²
  if (-6.0 < -1.5) ? 是!→ continue
  
  // i=1: r_pre = 1
  if (std::isinf(pre_col[1].total_cost()) || ...)
     → continue
  
  // i=2: r_pre = 0
  if (std::isinf(pre_col[0].total_cost()) || ...)
     → continue
}

结果

cost_table_[3][2]:
total_cost() = infinity

cost_table_ 最终状态(4 列 × 3 行)

列号 c=0 (t=0.0s):
  [0][0]: total_cost=0.0, optimal_speed=3.0, pre_point=nullptr ✓
  [0][1]: 未处理
  [0][2]: 未处理

列号 c=1 (t=1.0s):
  [1][0]: total_cost=infinity (加速度 -6.0 过大) ✗
  [1][1]: total_cost=0.0, optimal_speed=3.0, pre_point=[0][0] ✓
  [1][2]: total_cost=infinity (加速度 6.0 过大) ✗

列号 c=2 (t=2.0s):
  [2][0]: total_cost=infinity (前驱 [1][0] 不可达) ✗
  [2][1]: total_cost=infinity (减速度 -6.0 过大) ✗
  [2][2]: total_cost=0.0, optimal_speed=3.0, pre_point=[1][1] ✓

列号 c=3 (t=3.0s):
  [3][0]: total_cost=infinity ✗
  [3][1]: total_cost=infinity ✗
  [3][2]: total_cost=infinity (减速度 -6.0 过大) ✗
唯一可达路径:
  (0,0) → (1,1) → (2,2)

成本累积:
  cost_table_[0][0] = 0.0
  cost_table_[1][1] = 0.0 + 0.0 = 0.0
  cost_table_[2][2] = 0.0 + 0.0 = 0.0

回溯:
  [2][2].pre_point() → [1][1]
  [1][1].pre_point() → [0][0]
  [0][0].pre_point() → nullptr

障碍物代价

double DpStCost::GetObstacleCost(const StGraphPoint& st_graph_point) {
  const double s = st_graph_point.point().s();
  const double t = st_graph_point.point().t();

  double cost = 0.0;
for (const auto* obstacle : obstacles_) {
	auto boundary = obstacle->path_st_boundary();
    if (boundary.min_s() > FLAGS_speed_lon_decision_horizon) {
      continue;
    }

FLAGS_speed_lon_decision_horizon:纵向决策范围,默认是200m,如果障碍物不在纵向决策范围内,则忽略此障碍物

94cbc5c2387a41a7b216736c62bd8b84.png

if (boundary.IsPointInBoundary(st_graph_point.point())) {
      return kInf;
    }

如果当前ST图中的点(s,t)在障碍物的ST边界范围内,就将该点的代价设置为无穷

int boundary_index = boundary_map_[boundary.id()];
 if (boundary_cost_[boundary_index][st_graph_point.index_t()].first < 0.0) {
   boundary.GetBoundarySRange(t, &s_upper, &s_lower);
   boundary_cost_[boundary_index][st_graph_point.index_t()] =
       std::make_pair(s_upper, s_lower);
 } 

boundary_map_:可以理解为障碍物的ST边界id(障碍物id)与障碍物索引的对应关系
boundary_cost_:缓存每个障碍物在每个时间点的ST边界范围

if (boundary_cost_[boundary_index][st_graph_point.index_t()].first < 0.0) {

说明此时boundary_cost_[boundary_index][st_graph_point.index_t()]的边界范围还是初始值

GetBoundarySRange函数

用于查询指定时刻的 ST 边界纵向范围

bool STBoundary::GetBoundarySRange(const double curr_time, double* s_upper,
                                   double* s_lower) const {
  CHECK_NOTNULL(s_upper);
  CHECK_NOTNULL(s_lower);
  if (curr_time < min_t_ || curr_time > max_t_) {
    return false;
  }

  size_t left = 0;
  size_t right = 0;
  if (!GetIndexRange(lower_points_, curr_time, &left, &right)) {
    AERROR << "Fail to get index range.";
    return false;
  }

在障碍物下边界点集中,获取当前时间对应的索引范围

const double r =
(left == right
     ? 0.0
     : (curr_time - upper_points_[left].t()) /
           (upper_points_[right].t() - upper_points_[left].t()));

线性插值计算,当前ST图上的点所对应时间点在障碍物的ST边界上相对于right,left所对应的这段时间的比例

*s_upper = upper_points_[left].s() +
             r * (upper_points_[right].s() - upper_points_[left].s());

插值计算当前ST图上的点所对应时间点在障碍物的ST边界纵向边界上插值出来的上边界

*s_lower = lower_points_[left].s() +
             r * (lower_points_[right].s() - lower_points_[left].s());

插值计算当前ST图上的点所对应时间点在障碍物的ST边界纵向边界上插值出来的下边界

*s_upper = std::fmin(*s_upper, FLAGS_speed_lon_decision_horizon);
*s_lower = std::fmax(*s_lower, 0.0);

限制边界范围
到这GetBoundarySRange函数就介绍完了

boundary_cost_[boundary_index][st_graph_point.index_t()] =
          std::make_pair(s_upper, s_lower);

将获取到的通过障碍物ST纵向上下边界插值计算出的上下边界映射到ST图上对应的点上,这样就可以通过boundary_cost_根据时间查询障碍物的上下边界

else {
  s_upper = boundary_cost_[boundary_index][st_graph_point.index_t()].first;
  s_lower = boundary_cost_[boundary_index][st_graph_point.index_t()].second;
}

如果ST图上的某一个点,对应同一个障碍物,同一个时间点,则使用缓存中boundary_cost_已经计算好的上下边界,不再重新计算

 if (s < s_lower) {

表示ST图上某个状态点的纵向距离值在障碍物后方
计算跟随成本

const double follow_distance_s = config_.safe_distance();

跟车安全距离默认是20m

8f3c4ab5ca22468d82849aebe9435e47.png

if (s + follow_distance_s < s_lower) {
        continue;
      }

可以理解为当前这个ST图上的点的纵向距离加上跟车安全距离之后还是在障碍物后方,表示自车行驶到ST图这个时间点的时候不会和障碍物发生碰撞,所以不需要计算代价

else {
  auto s_diff = follow_distance_s - s_lower + s;
  cost += config_.obstacle_weight() * config_.default_obstacle_cost() *
          s_diff * s_diff;
}

使用距离差s_diff表示危险程度,使用距离差的二次函数计算障碍物成本
config_.default_obstacle_cost():障碍物成本的放大倍数,默认​1e^{4}

68c4acd2d7f34c61a2f24d77fde7ce90.png

config_.obstacle_weight():是障碍物的权重系数,巡航时默认是1
ST图上点的障碍物成本代价,是与该点相关的障碍物的代价累加起来的总代价

else if (s > s_upper) {

表示ST图上某个状态点的纵向距离值在障碍物前方
计算超车成本

const double overtake_distance_s =
          StGapEstimator::EstimateSafeOvertakingGap();

安全超越距离

if (s > s_upper + overtake_distance_s) {  // or calculated from velocity
        continue;
      }

如果当前ST状态点的纵向距离大于安全超越所需的最小距离,表示该状态点不需要计算障碍物代价

else {
   auto s_diff = overtake_distance_s + s_upper - s;
   cost += config_.obstacle_weight() * config_.default_obstacle_cost() *
           s_diff * s_diff;
 }

如果当前ST状态点的纵向距离小于安全超越所需的最小距离,则同样使用距离差s_diff表示危险程度,使用距离差的二次函数计算障碍物成本,同样是所有相关障碍物代价累加起来的总代价

return cost * unit_t_;

其实本质上是
​\int_{0}^{t} cost(t)dt
然后离散化,使用求和近似计算积分
​\sum_{i = 0}^{t - 1} cost[i] \Delta t
​(cost[0] + cost[1] + cost[2] + ... +cost[t-1])\Delta t
转换为代码就是cost * unit_t_
注意这里的cost * unit_t_只是上面​(cost[0] + cost[1] + cost[2] + ... +cost[t-1])\Delta t代价中的一项障碍物代价
到这GetObstacleCost函数就介绍完了

  auto& cost_cr = cost_table_[c][r];

  cost_cr.SetObstacleCost(dp_st_cost_.GetObstacleCost(cost_cr));
  if (cost_cr.obstacle_cost() > std::numeric_limits<double>::max()) {
    return;
  }

然后将计算好的障碍物代价存储到cost_table_中,如果障碍物成本超过最大值(即无穷大),说明发生碰撞,直接返回

距离终点代价

GetSpatialPotentialCost函数

double DpStCost::GetSpatialPotentialCost(const StGraphPoint& point) {
  return (total_s_ - point.point().s()) * config_.spatial_potential_penalty();
}

距离路径规划终点越远代价越大,越近代价越小
total_s_:是笛卡尔坐标系下路径规划出来的轨迹长度

3eae572b6b9a4e58ba79685f7751a71e.png

cost_cr.SetSpatialPotentialCost(dp_st_cost_.GetSpatialPotentialCost(cost_cr));

然后将计算好的距离终点代价存储到cost_table_中

CheckOverlapOnDpStGraph函数

检查从当前状态点到前驱状态点的直线路径是否与障碍物碰撞

bool CheckOverlapOnDpStGraph(const std::vector<const STBoundary*>& boundaries,
                             const StGraphPoint& p1, const StGraphPoint& p2) {
...
if (boundary->HasOverlap({p1.point(), p2.point()})) {
  return true;
}
bool Polygon2d::HasOverlap(const LineSegment2d &line_segment) const {
LineSegment2d::LineSegment2d(const Vec2d &start, const Vec2d &end)
    : start_(start), end_(end) {
  const double dx = end_.x() - start_.x();
  const double dy = end_.y() - start_.y();
  length_ = hypot(dx, dy);
  unit_direction_ =
      (length_ <= kMathEpsilon ? Vec2d(0, 0)
                               : Vec2d(dx / length_, dy / length_));
  heading_ = unit_direction_.Angle();
}
bool Polygon2d::HasOverlap(const LineSegment2d &line_segment) const {
  CHECK_GE(points_.size(), 3U);
  if ((line_segment.start().x() < min_x_ && line_segment.end().x() < min_x_) ||
      (line_segment.start().x() > max_x_ && line_segment.end().x() > max_x_) ||
      (line_segment.start().y() < min_y_ && line_segment.end().y() < min_y_) ||
      (line_segment.start().y() > max_y_ && line_segment.end().y() > max_y_)) {
    return false;
  }

这里先用线段和障碍物ST边界的笛卡尔坐标做了粗糙的碰撞检测

if (std::any_of(line_segments_.begin(), line_segments_.end(),
                  [&](const LineSegment2d &poly_seg) {
                    return poly_seg.HasIntersect(line_segment);
                  })) {
    return true;
  }
bool LineSegment2d::HasIntersect(const LineSegment2d &other_segment) const {
  Vec2d point;
  return GetIntersect(other_segment, &point);
}

GetIntersect函数

已经在planning模块(14) task SPEED_BOUNDS_PRIORI_DECIDER 介绍过了,可以回顾一下

 if (CheckOverlapOnDpStGraph(st_graph_data_.st_boundaries(), cost_cr,
                             cost_init)) {
   return;
 }

碰撞意味着这个状态点在物理上不可达,必须放弃,否则会选择危险的路径

    cost_cr.SetTotalCost(
        cost_cr.obstacle_cost() + cost_cr.spatial_potential_cost() +
        cost_init.total_cost() +
        CalculateEdgeCostForSecondCol(r, speed_limit, cruise_speed));

设置ST图当前状态点的总代价逻辑
当前状态点的总代价=障碍物代价+距离终点代价+前驱状态点总代价+边代价

边的代价

CalculateEdgeCostForSecondCol函数

计算初始状态点到第二列边的代价

double GriddedPathTimeGraph::CalculateEdgeCostForSecondCol(
    const uint32_t row, const double speed_limit, const double cruise_speed) {
  double init_speed = init_point_.v();
  double init_acc = init_point_.a();
  const STPoint& pre_point = cost_table_[0][0].point();
  const STPoint& curr_point = cost_table_[1][row].point();
  return dp_st_cost_.GetSpeedCost(pre_point, curr_point, speed_limit,
                                  cruise_speed) +
         dp_st_cost_.GetAccelCostByTwoPoints(init_speed, pre_point,
                                             curr_point) +
         dp_st_cost_.GetJerkCostByTwoPoints(init_speed, init_acc, pre_point,
                                            curr_point);
}

init_speed:初始状态点速度
init_acc:初始状态点加速度
pre_point:第二列的状态点的前驱都是初始状态点
curr_point:当前状态点

速度代价

GetSpeedCost函数

dp_st_cost_.GetSpeedCost(pre_point, curr_point, speed_limit,cruise_speed)
double DpStCost::GetSpeedCost(const STPoint& first, const STPoint& second,
                              const double speed_limit,
                              const double cruise_speed) const {
double cost = 0.0;
const double speed = (second.s() - first.s()) / unit_t_;
if (speed < 0) {
  return kInf;
}

计算平均速度,计算出的速度是负数,表示车辆在倒车直接返回无穷大代价

const double max_adc_stop_speed = common::VehicleConfigHelper::Instance()
                                        ->GetConfig()
                                        .vehicle_param()
                                        .max_abs_speed_when_stopped();

max_abs_speed_when_stopped:是一个速度阈值,默认是0.2m/s

  • 速度 < 阈值 → 车辆视为"静止"
  • 速度 ≥ 阈值 → 车辆视为"运动"

2974f98d9a394ce293fe56d2eadd7a19.png

  if (speed < max_adc_stop_speed && InKeepClearRange(second.s())) {
    // first.s in range
    cost += config_.keep_clear_low_speed_penalty() * unit_t_ *
            config_.default_speed_cost();
  }

如果自车在禁停区并且接近停车状态时,施加惩罚
config_.keep_clear_low_speed_penalty():惩罚强度,默认为10
unit_t_:时间步长,默认1.0s
config_.default_speed_cost():代价基数,默认1.0e3

double det_speed = (speed - speed_limit) / speed_limit;

计算相对速度偏差

2b611bc23df14ccd8a684f55c9f9abd0.png

cost += config_.exceed_speed_penalty() * config_.default_speed_cost() *
            (det_speed * det_speed) * unit_t_;

超速惩罚代价 += 惩罚权重(1.0e3) * 代价基数(1.0e3) * (相对偏差)² * 时间步长

cost += config_.low_speed_penalty() * config_.default_speed_cost() *
            -det_speed * unit_t_

低速惩罚代价 += 惩罚权重(10.0) * 代价基数(1.0e3) * 相对偏差 * 时间步长

if (config_.enable_dp_reference_speed()) {
    double diff_speed = speed - cruise_speed;
    cost += config_.reference_speed_penalty() * config_.default_speed_cost() *
            fabs(diff_speed) * unit_t_;
  }

当启用参考速度功能时,惩罚偏离期望巡航速度的行为

加速度代价

GetAccelCostByTwoPoints函数

double DpStCost::GetAccelCostByTwoPoints(const double pre_speed,
                                         const STPoint& pre_point,
                                         const STPoint& curr_point) {
  double current_speed = (curr_point.s() - pre_point.s()) / unit_t_;
  double accel = (current_speed - pre_speed) / unit_t_;

计算当前加速度
​\dot{s}=\frac{s_{1} - 0}{t_{1} - 0}
​\ddot{s}=\frac{\dot{s} - 0}{t_{1} - 0}

GetAccelCost函数

 const double accel_sq = accel * accel;//加速度平方
 double max_acc = config_.max_acceleration();//最大加速度,2m/s^2
 double max_dec = config_.max_deceleration();//最大减速度, -4m/s^2
 double accel_penalty = config_.accel_penalty();//加速惩罚权重, 1.0
 double decel_penalty = config_.decel_penalty();//减速惩罚权重, 1.0

if (accel > 0.0) {
      cost = accel_penalty * accel_sq;
    } else {
      cost = decel_penalty * accel_sq;
    }

加速代价 = accel_penalty × accel²
减速代价 = decel_penalty × accel²

cost += accel_sq * decel_penalty * decel_penalty /
                (1 + std::exp(1.0 * (accel - max_dec))) +
            accel_sq * accel_penalty * accel_penalty /
                (1 + std::exp(-1.0 * (accel - max_acc)));
    accel_cost_.at(accel_key) = cost;

边界额外惩罚

\Delta J = \frac{a^2 \cdot w_{\text{dec}}^2}{1 + e^{(a - max_{dec})}} + \frac{a^2 \cdot w_{\text{acc}}^2}{1 + e^{-(a - max_{acc})}}

到这GetAccelCost函数就介绍完了,GetAccelCostByTwoPoints函数也就介绍完了

GetJerkCostByTwoPoints函数

计算当前加加速度
​\dot{s}=\frac{s_{1} - 0}{t_{1} - 0}
​\ddot{s}=\frac{\dot{s} - \left | \vec{v} \right | }{t_{1} - 0}
​\dddot{s}=\frac{\ddot{s} - \left | \vec{a} \right | }{t_{1} - 0}

const double curr_speed = (curr_point.s() - pre_point.s()) / unit_t_;
const double curr_accel = (curr_speed - pre_speed) / unit_t_;
const double jerk = (curr_accel - pre_acc) / unit_t_;

加加速度代价

JerkCost函数

if (jerk > 0) {
      cost = config_.positive_jerk_coeff() * jerk_sq * unit_t_;
    }

positive_jerk_coeff默认是1.0
​cost = \omega_{pos} \cdot j^{2} \cdot \Delta t, j>0

else {
      cost = config_.negative_jerk_coeff() * jerk_sq * unit_t_;
    }

​cost = \omega_{neg} \cdot j^{2} \cdot \Delta t, j>0
negative_jerk_coeff默认是1.0

return cost;

这里没有乘以unit_t_,因为在上面已经乘过了,为什么乘以unit_t_上面已经介绍过了
到这JerkCost函数就介绍完了,GetJerkCostByTwoPoints函数也介绍完了

return dp_st_cost_.GetSpeedCost(pre_point, curr_point, speed_limit,
                                  cruise_speed) +
         dp_st_cost_.GetAccelCostByTwoPoints(init_speed, pre_point,
                                             curr_point) +
         dp_st_cost_.GetJerkCostByTwoPoints(init_speed, init_acc, pre_point,
                                            curr_point);

最后得到边代价=速度代价+加速度代价+加加速度代价
到这CalculateEdgeCostForSecondCol函数就介绍完了

CalculateEdgeCost和CalculateEdgeCostForThirdCol的处理流程和CalculateEdgeCostForSecondCol差不多

RetrieveSpeedProfile函数

  for (const StGraphPoint& cur_point : cost_table_.back()) {
    if (!std::isinf(cur_point.total_cost()) &&
        cur_point.total_cost() < min_cost) {
      best_end_point = &cur_point;
      min_cost = cur_point.total_cost();
    }
  }

遍历最后一列的所有点
筛选可达点
找到成本最小的点作为最优终点
为后续的路径回溯提供起点

std::vector<SpeedPoint> speed_profile;
const StGraphPoint* cur_point = best_end_point;
PrintPoints debug_res("dp_result");
while (cur_point != nullptr) {
  ADEBUG << "Time: " << cur_point->point().t();
  ADEBUG << "S: " << cur_point->point().s();
  ADEBUG << "V: " << cur_point->GetOptimalSpeed();
  SpeedPoint speed_point;
  debug_res.AddPoint(cur_point->point().t(), cur_point->point().s());
  speed_point.set_s(cur_point->point().s());
  speed_point.set_t(cur_point->point().t());
  speed_profile.push_back(speed_point);
  cur_point = cur_point->pre_point();
}
  • 路径回溯:从最优终点沿着pre_point()回到起点
  • 生成速度曲线:将路径点转换为SpeedPoint格式
 std::reverse(speed_profile.begin(), speed_profile.end());

将速度曲线反转,从"终点→起点"变为"起点→终点"的时间顺序

if (speed_profile.front().t() > kEpsilon ||
speed_profile.front().s() > kEpsilon) {
const std::string msg = "Fail to retrieve speed profile.";
AERROR << msg;
return Status(ErrorCode::PLANNING_ERROR, msg);
}

验证起点:确保速度曲线从原点(t=0, s=0)开始

for (size_t i = 0; i + 1 < speed_profile.size(); ++i) {
const double v = (speed_profile[i + 1].s() - speed_profile[i].s()) /
                 (speed_profile[i + 1].t() - speed_profile[i].t() + 1e-3);
speed_profile[i].set_v(v);
}

*speed_data = SpeedData(speed_profile);

计算每个速度点的速度值,并生成最终的速度数据对象,这里没有使用DP时的GetOptimalSpeed()速度,而是又通过差分重新计算了速度
到这reference_line_info->mutable_speed_data()的数据就计算完了