vlambda博客
学习文章列表

开发者说丨动态规划及其在Apollo项目Planning模块的应用


动态规划的英文为:Dynamic Programming,这里的“Programming”并非指编写程序代码,而是指一种表格计算法(A tabular method),即基于表格查询的方法计算得到最优结果,因此中文将其翻译成“动态规划”不甚严谨。关于动态规划算法的原理,MIT出版的专著:“Introduction to Algorithms Third Edition (Thomas H. Cormen, Charles E. leiserson, Ronald L. Rivest, Clifford Stein)”(中文版《算法导论》)讲解得不错,本文的算法原理及示例均摘自该书。


本文由社区荣誉布道师——贺志国撰写动态规划及其在Apollo项目Planning模块的应用进行了详细讲解,希望这篇文章能给感兴趣的开发者带来更多帮助。


  以下,ENJOY  



开发者说丨动态规划及其在Apollo项目Planning模块的应用


动态规划与分治(The Divide-and-Conquer Method)有些类似,也是将问题分解为多个子问题,并且基于子问题的结果获得最终解。二者的区别是,分治法将初始问题划分为多个不关联(Disjoint)的子问题(Subproblem)(即子问题相互之间互不依赖),递归地解决子问题,然后将子问题的解合并得到初始问题的解。与之相反,动态规划法分解得到的子问题是相互重叠的(Overlap),即子问题依赖于子子问题(Subsubproblem),子子问题又进一步依赖于下一级的子子问题,这样不断循环直至抵达不需分解的初始条件。在求解过程中,为了避免重复计算子子问题从而提高算法效率,需要将一系列子子问题的解保存到一张表中(table,C++编程一般使用std::arraystd::vectorstd::list实现),这也就是动态规划又被称为查表计算法的原因。


动态规划一般应用于最优化问题(Optimization Problems)。这类问题一般存在多个解,每个解都具有一个度量值,我们期望得到具有度量值最优(即取最大或最小值)的解。该最优解一般称为最优化问题的一个解。注意,这个解并非唯一,因为最优化问题可能存在多个最优解。


构建一个动态规划算法的一般步骤如下:


1、刻画出一个最优解的结构特征(即使用数学表达式来表述一个最优解);

2、迭代定义一个最优解的度量值;

3、计算每个最优解的度量值,通常采用自下而上的方式;

4、根据计算得到的信息构建出原问题的一个最优解。步骤1-3是使用动态规划求解问题的基础形式。如果我们只需获得最优解的度量值而非最优解本身,则可以忽略步骤4。



开发者说丨动态规划及其在Apollo项目Planning模块的应用


Apollo项目Planning模块的EMPlanner中使用动态规划生成代价(Cost)最小的多项式路径(DP路径,见Apollo项目中的DPRoadGraph类)和速度(DP速度,见Apollo项目中的DpStGraph类),DP路径算法和DP速度算法的示意性描述如下图所示(来自百度Apollo项目公开课PPT):


开发者说丨动态规划及其在Apollo项目Planning模块的应用

▲图1. DP路径算法


开发者说丨动态规划及其在Apollo项目Planning模块的应用

图2. DP速度算法


DP路径算法的基本思路是,基于给定的一条中心路径(称为参考线,ReferenceLine)和车道边界条件,每隔一定间隔的纵向距离(称为不同级(Level)上的s值)对道路截面进行均匀采样(与中心点的横向偏移值称为为l值),这样会得到图中黑点所示的采样点(这些采样点称为航点,Waypoint)数组。基于一定的规则,可以给出各航点迁移的代价值(Cost)。航点迁移不一定需要逐级进行,可以从第一级跳到第三级甚至终点,只要迁移代价值最小化即可,这显然满足动态规划的求解思路。


DP速度算法的基本思路是,在DP路径算法生成一条可行驶的路径后,从起点开始,考虑避开路径中的所有障碍物,并且让加减速最为平顺,以最优的速度曲线(即t-s平面中的绿色曲线)安全抵达终点,这也可以使用动态规划的思路求解。


开发者说丨动态规划及其在Apollo项目Planning模块的应用


航点数组生成

 1// 采样获得路径航点
2bool DPRoadGraph::SamplePathWaypoints(
3    const common::TrajectoryPoint &init_point,
4    std::vector<std::vector > * const points) {
5  CHECK_NOTNULL(points);
6
7   // 最小采样距离
8   const  double kMinSampleDistance =  40.0;
9   // 总长度 = min(初始点 + max(初始速度 × 8, 最小采样距离), 参考线长度)
10   const  double total_length =  std::fmin(
11      init_sl_point_.s() +  std::fmax(init_point.v() *  8.0, kMinSampleDistance),
12      reference_line_.Length());
13
14   // 采样前视时长
15   constexpr  double kSamplePointLookForwardTime =  4.0;We can just use `sl = common::util::MakeSLPoint(s, l);` . 
16   // 采样步长 = 初始速度 × 采样前视时长,要求:
17   // step_length_min(默认值:8) <= 采样步长 <= step_length_max(默认值:15)
18   const  double level_distance =
19      common::math::Clamp(init_point.v() * kSamplePointLookForwardTime,
20                          config_.step_length_min(), config_.step_length_max());
21   // 累计轨迹弧长
22   double accumulated_s = init_sl_point_.s();
23   // 上次轨迹弧长
24   double prev_s = accumulated_s;
25   // 累计轨迹弧长小于总长度时,将累计轨迹弧长每次加上采样步长,进行循环采样
26   for ( std:: size_t i =  0; accumulated_s < total_length; ++i) {
27    accumulated_s += level_distance;
28     if (accumulated_s + level_distance /  2.0 > total_length) {
29      accumulated_s = total_length;
30    }
31     // 本次轨迹弧长:取累计轨迹弧长与总长度之间的最小值
32     const  double s =  std::fmin(accumulated_s, total_length);
33     // 最小允许采样步长
34     constexpr  double kMinAllowedSampleStep =  1.0;
35     // 若本次轨迹弧长与上次轨迹弧长间的差值小于最小允许采样步长,跳过本次采样
36     if ( std:: fabs(s - prev_s) < kMinAllowedSampleStep) {
37       continue;
38    }
39    prev_s = s;
40
41     // 左车道宽
42     double left_width =  0.0;
43     // 右车道宽
44     double right_width =  0.0;
45    reference_line_.GetLaneWidth(s, &left_width, &right_width);
46
47     // 边界缓冲
48     constexpr  double kBoundaryBuff =  0.20;
49     const  auto &vehicle_config =
50        common::VehicleConfigHelper::instance()->GetConfig();
51     const  double half_adc_width = vehicle_config.vehicle_param().width() /  2.0;
52     // 右侧允许宽度 = 右车道宽 - 半车宽 - 边界缓冲
53     const  double eff_right_width = right_width - half_adc_width - kBoundaryBuff;
54     // 左侧允许宽度 = 左车道宽 - 半车宽 - 边界缓冲
55     const  double eff_left_width = left_width - half_adc_width - kBoundaryBuff;
56     // 每步采样点数
57     const  size_t num_sample_per_level =
58        FLAGS_use_navigation_mode ? config_.navigator_sample_num_each_level()
59                                  : config_.sample_points_num_each_level();
60     // 默认横向采样间隔
61     double kDefaultUnitL =  1.2 / (num_sample_per_level -  1);
62     if (reference_line_info_.IsChangeLanePath() && !IsSafeForLaneChange()) {
63      kDefaultUnitL =  1.0;
64    }
65     // 横向采样距离
66     const  double sample_l_range = kDefaultUnitL * (num_sample_per_level -  1);
67     // 右采样边界(车辆右侧为负值)
68     double sample_right_boundary = -eff_right_width;
69     // 左采样边界(车辆左侧为正值)
70     double sample_left_boundary = eff_left_width;
71     // 参考线正在改变车道时
72     if (reference_line_info_.IsChangeLanePath()) {
73       // 右采样边界取右采样边界与初始点横向偏移之间的最小值
74      sample_right_boundary =  std::fmin(-eff_right_width, init_sl_point_.l());
75       // 左采样边界取左采样边界与初始点横向偏移之间的最大值
76      sample_left_boundary =  std::fmax(eff_left_width, init_sl_point_.l());
77
78       // 若初始点横向偏移 > 左侧允许宽度,则将右侧采样边界设置为右侧采样边界与(初始点横向偏移
79       // - 横向采样距离)之间的最大值
80       if (init_sl_point_.l() > eff_left_width) {
81        sample_right_boundary =  std::fmax(sample_right_boundary,
82                                          init_sl_point_.l() - sample_l_range);
83      }
84       // 若初始点横向偏移 < 右侧允许宽度,则将左侧采样边界设置为左侧采样边界与(初始点横向偏移
85       // + 横向采样距离)之间的最小值
86       if (init_sl_point_.l() < eff_right_width) {
87        sample_left_boundary =  std::fmin(sample_left_boundary,
88                                         init_sl_point_.l() + sample_l_range);
89      }
90    }
91
92     // 横向采样距离数组
93     std:: vector< double> sample_l;
94     // 参考线正在改变车道且改变车道不安全时,将当前参考线到其他参考线的偏移值存储到横向采样距离数组
95     if (reference_line_info_.IsChangeLanePath() && !IsSafeForLaneChange()) {
96      sample_l.push_back(reference_line_info_.OffsetToOtherReferenceLine());
97    }  else {
98       // 其他情形,从右采样边界到左采样边界,按照每步采样点数进行均匀采样,并将结果存储到横向采样距离数组
99      common::util::uniform_slice(sample_right_boundary, sample_left_boundary,
100                                  num_sample_per_level -  1, &sample_l);
101       if (HasSidepass()) {
102         // currently only left nudge is supported. Need road hard boundary for
103         // both sides
104        sample_l.clear();
105         switch (sidepass_.type()) {
106           case ObjectSidePass::LEFT: {
107             // 左侧绕行:将(左侧允许宽度 + 左侧绕行距离)存储到横向采样距离数组
108            sample_l.push_back(eff_left_width + config_.sidepass_distance());
109             break;
110          }
111           case ObjectSidePass::RIGHT: {
112             // 右侧绕行:将-(右侧允许宽度 + 右侧绕行距离)存储到横向采样距离数组
113            sample_l.push_back(-eff_right_width - config_.sidepass_distance());
114             break;
115          }
116           default:
117             break;
118        }
119      }
120    }
121     // 本次采样点数组
122     std:: vector  level_points;
123    planning_internal::SampleLayerDebug sample_layer_debug;
124     for ( size_t j =  0; j < sample_l.size(); ++j) {
125       // 横向偏移值
126       const  double l = sample_l[j];
127       constexpr  double kResonateDistance =  1e-3;
128      common::SLPoint sl;
129       // 若为奇数采样点或者(总长度 - 累计轨迹弧长)几乎为0即已抵达采样终点,
130       // 则直接将当前采样点坐标设置为(s, l)
131       if (j %  2 ==  0 ||
132          total_length - accumulated_s <  2.0 * kResonateDistance) {
133        sl = common::util::MakeSLPoint(s, l);
134      }  else {
135         // 其他情形,将当前采样点坐标设置为(min(总长度,s+误差),l)
136        sl = common::util::MakeSLPoint(
137             std::fmin(total_length, s + kResonateDistance), l);
138      }
139      sample_layer_debug.add_sl_point()->CopyFrom(sl);
140       // 将当前采样点坐标存储到本次采样点数组
141      level_points.push_back( std::move(sl));
142    }
143     // 若参考线未改变车道且不绕行,则将横向偏移值为0的采样点(即沿参考线方向的采样点)也加入本次采样点数组
144     if (!reference_line_info_.IsChangeLanePath() && !HasSidepass()) {
145       auto sl_zero = common::util::MakeSLPoint(s,  0.0);
146      sample_layer_debug.add_sl_point()->CopyFrom(sl_zero);
147      level_points.push_back(sl_zero);
148    }
149
150     if (!level_points.empty()) {
151      planning_debug_->mutable_planning_data()
152          ->mutable_dp_poly_graph()
153          ->add_sample_layer()
154          ->CopyFrom(sample_layer_debug);
155       // 将本次的所有采样点存储到总采样点数组
156      points->emplace_back(level_points);
157    }
158  }
159   return  true;
160}

<左右滑动以查看完整代码>


基于航点数组,使用动态规划算法求解代价值最小的路径。


 1bool DPRoadGraph::GenerateMinCostPath(
2    const std::vector<const PathObstacle *> &obstacles,
3    std::vector  *min_cost_path) {
4  CHECK(min_cost_path != nullptr);
5
6   // 基于当前参考线及初始点,生成候选路径采样点数组
7   // 路径航点(path_waypoints)里面的每个vecotr存储相同s值(轨迹累计弧长)下的多个采样点
8  std::vector<std::vector > path_waypoints;
9   if (!SamplePathWaypoints(init_point_, &path_waypoints) ||
10      path_waypoints.size() <  1) {
11    AERROR <<  "Fail to sample path waypoints! reference_line_length = "
12           << reference_line_.Length();
13     return  false;
14  }
15   // 将初始点加入到路径航点数组的最前面
16  path_waypoints.insert(path_waypoints.begin(),
17                        std::vector {init_sl_point_});
18   if (path_waypoints.size() <  2) {
19    AERROR <<  "Too few path_waypoints.";
20     return  false;
21  }
22
23   // 输出路径航点调试信息
24   for (uint32_t i =  0; i < path_waypoints.size(); ++i) {
25     const auto &level_waypoints = path_waypoints.at(i);
26     for (uint32_t j =  0; j < level_waypoints.size(); ++j) {
27      ADEBUG <<  "level[" << i <<  "], "
28             << level_waypoints.at(j).ShortDebugString();
29    }
30  }
31
32   // 读取车辆配置信息
33   const auto &vehicle_config =
34      common::VehicleConfigHelper::instance()->GetConfig();
35
36   // 轨迹代价
37  TrajectoryCost trajectory_cost(
38      config_, reference_line_, reference_line_info_.IsChangeLanePath(),
39      obstacles, vehicle_config.vehicle_param(), speed_data_, init_sl_point_);
40
41   // 最小代价值路图节表点链表
42  std::list<std::list </std::list
</std::vector
> graph_nodes;
43  graph_nodes.emplace_back();
44  graph_nodes.back().emplace_back(init_sl_point_, nullptr, ComparableCost());
45  auto &front = graph_nodes.front().front();
46  size_t total_level = path_waypoints.size();
47  // 采用自下而上的动态规划算法,迭代更新最小代价值
48  // graph_nodes存储的就是各级(level)路径航点(path_waypoints)所包含的最小代价航点
49  // graph_nodes.back()(即最后一条航点链表)就是我们所需的最小代价航点链表
50  for (std::size_t level = 1; level < path_waypoints.size(); ++level) {
51    const auto &prev_dp_nodes = graph_nodes.back();
52    const auto &level_points = path_waypoints[level];
53
54    graph_nodes.emplace_back();
55
56    for (size_t i = 0; i < level_points.size(); ++i) {
57      const auto &cur_point = level_points[i];
58
59      graph_nodes.back().emplace_back(cur_point, nullptr);
60      auto &cur_node = graph_nodes.back().back();
61      // 采用多线程并行计算最小代价值航点
62      if (FLAGS_enable_multi_thread_in_dp_poly_path) {
63        PlanningThreadPool::instance()->Push(std::bind(
64            &DPRoadGraph::UpdateNode, this, std::ref(prev_dp_nodes), level,
65            total_level, &trajectory_cost, &(front), &(cur_node)));
66
67      } else {
68        // 采用单线程计算最小代价值航点
69        UpdateNode(prev_dp_nodes, level, total_level, &trajectory_cost, &front,
70                   &cur_node);
71      }
72    }
73    // 多线程模式下的同步
74    if (FLAGS_enable_multi_thread_in_dp_poly_path) {
75      PlanningThreadPool::instance()->Synchronize();
76    }
77  }
78
79  // graph_nodes.back()(即最后一条航点链表)就是我们所需的最小代价航点链表
80  // find best path
81  DPRoadGraphNode fake_head;
82  for (const auto &cur_dp_node : graph_nodes.back()) {
83    fake_head.UpdateCost(&cur_dp_node, cur_dp_node.min_cost_curve,
84                         cur_dp_node.min_cost);
85  }
86
87  // 从终点顺藤摸瓜向起点逐个找出最小代价值航点,并将其加入min_cost_path
88  const auto *min_cost_node = &fake_head;
89  while (min_cost_node->min_cost_prev_node) {
90    min_cost_node = min_cost_node->min_cost_prev_node;
91    min_cost_path->push_back(*min_cost_node);
92  }
93  if (min_cost_node != &graph_nodes.front().front()) {
94    return false;
95  }
96
97  // 将航点顺序调整为起点到终点
98  std::reverse(min_cost_path->begin(), min_cost_path->end());
99
100  for (const auto &node : *min_cost_path) {
101    ADEBUG << "min_cost_path: " << node.sl_point.ShortDebugString();
102    planning_debug_->mutable_planning_data()
103        ->mutable_dp_poly_graph()
104        ->add_min_cost_point()
105        ->CopyFrom(node.sl_point);
106  }
107  return true;
108}
109
110// 在当前level下,获得一条代价值最小的航点链表
111void DPRoadGraph::UpdateNode(const std::list  &prev_nodes,
112                              const uint32_t level,  const uint32_t total_level,
113                             TrajectoryCost *trajectory_cost,
114                             DPRoadGraphNode *front,
115                             DPRoadGraphNode *cur_node) {
116  DCHECK_NOTNULL(trajectory_cost);
117  DCHECK_NOTNULL(front);
118  DCHECK_NOTNULL(cur_node);
119   for ( const auto &prev_dp_node : prev_nodes) {
120     const auto &prev_sl_point = prev_dp_node.sl_point;
121     const auto &cur_point = cur_node->sl_point;
122    double init_dl =  0.0;
123    double init_ddl =  0.0;
124     if (level ==  1) {
125      init_dl = init_frenet_frame_point_.dl();
126      init_ddl = init_frenet_frame_point_.ddl();
127    }
128     // 生成当前点到前一level所有航点的的曲线
129    QuinticPolynomialCurve1d curve(prev_sl_point.l(), init_dl, init_ddl,
130                                   cur_point.l(),  0.00.0,
131                                   cur_point.s() - prev_sl_point.s());
132
133     if (!IsValidCurve(curve)) {
134       continue;
135    }
136     const auto cost =
137        trajectory_cost->Calculate(curve, prev_sl_point.s(), cur_point.s(),
138                                   level, total_level) +
139        prev_dp_node.min_cost;
140     // 根据代价最小的原则,在前一level所有航点中找到与当前点连接代价最小的航点,
141     // 将结果存储于prev_dp_node中
142    cur_node->UpdateCost(&prev_dp_node, curve, cost);
143
144     // 尝试将当前点直接连接到初始点,看其代价是否比当前点到前一level航点的最小代价还小,
145     // 若小于则将最小代价航点更新。这种情况一般只会存在于改变车道的情形。
146     // try to connect the current point with the first point directly
147     // only do this at lane change
148     if (level >=  2) {
149      init_dl = init_frenet_frame_point_.dl();
150      init_ddl = init_frenet_frame_point_.ddl();
151      QuinticPolynomialCurve1d curve(init_sl_point_.l(), init_dl, init_ddl,
152                                     cur_point.l(),  0.00.0,
153                                     cur_point.s() - init_sl_point_.s());
154       if (!IsValidCurve(curve)) {
155         continue;
156      }
157       const auto cost = trajectory_cost->Calculate(
158          curve, init_sl_point_.s(), cur_point.s(), level, total_level);
159      cur_node->UpdateCost(front, curve, cost);
160    }
161  }
162}

<左右滑动以查看完整代码>

开发者说丨动态规划及其在Apollo项目Planning模块的应用

 1// 从st图中寻找代价值最小的速度曲线
2// s:行驶距离,纵坐标;
3// t:行驶时间,横坐标
4// SpeedData* const speed_data表示speed_data本身(即指针自身)不能被修改,
5// 但speed_data指向的内容可被修改,该函数就是通过它返回最优速度数据的。
6Status DpStGraph::Search(SpeedData* const speed_data) {
7  constexpr double kBounadryEpsilon = 1e-2;
8  // 对边界条件进行初步筛选
9  for (const auto& boundary : st_graph_data_.st_boundaries()) {
10    // 若边界类型为禁停区,直接跳过
11    if (boundary->boundary_type() == StBoundary::BoundaryType::KEEP_CLEAR) {
12      continue;
13    }
14    // 若边界位于(0.0, 0.0)(即起始位置)或者边界的min_t和min_s比边界最小分辨率
15    // kBounadryEpsilon还小,则将速度点的s值设为0.0,t值均匀采样递增。
16    if (boundary->IsPointInBoundary({0.00.0}) ||
17        (std::fabs(boundary->min_t()) < kBounadryEpsilon &&
18         std::fabs(boundary->min_s()) < kBounadryEpsilon)) {
19      std::vector  speed_profile;
20       double t =  0.0;
21       for ( int i =  0; i < dp_st_speed_config_. matrix_dimension_t();
22           ++i, t += unit_t_) {
23        SpeedPoint speed_point;
24        speed_point.set_s( 0.0);
25        speed_point. set_t(t);
26        speed_profile.emplace_back(speed_point);
27      }
28      speed_data->set_speed_vector(speed_profile);
29       return Status::OK();
30    }
31  }
32
33   // 若st图数据的边界条件为空,意味着前方没有障碍物,匀速前进即可。
34   // speed_profile[i] = (0.0 + i * unit_s_, 0.0 + i * unit_t_);
35   if (st_graph_data_.st_boundaries().empty()) {
36    ADEBUG <<  "No path obstacles, dp_st_graph output default speed profile.";
37     std:: vector  speed_profile;
38     double s =  0.0;
39     double t =  0.0;
40     for ( int i =  0; i < dp_st_speed_config_. matrix_dimension_t() &&
41                    i < dp_st_speed_config_.matrix_dimension_s();
42         ++i, t += unit_t_, s += unit_s_) {
43      SpeedPoint speed_point;
44      speed_point.set_s(s);
45      speed_point. set_t(t);
46      speed_profile.emplace_back(speed_point);
47    }
48    speed_data->set_speed_vector(speed_profile);
49     return Status::OK();
50  }
51
52   // 初始化代价表cost_table_
53   if (!InitCostTable().ok()) {
54     const  std:: string msg =  "Initialize cost table failed.";
55    AERROR << msg;
56     return Status(ErrorCode::PLANNING_ERROR, msg);
57  }
58   // 计算代价表cost_table_中所有节点的总代价
59   if (!CalculateTotalCost().ok()) {
60     const  std:: string msg =  "Calculate total cost failed.";
61    AERROR << msg;
62     return Status(ErrorCode::PLANNING_ERROR, msg);
63  }
64   // 通过计算得到的代价表cost_table_中所有节点的总代价,获取速度数据
65   if (!RetrieveSpeedProfile(speed_data).ok()) {
66     const  std:: string msg =  "Retrieve best speed profile failed.";
67    AERROR << msg;
68     return Status(ErrorCode::PLANNING_ERROR, msg);
69  }
70   return Status::OK();
71}
72
73 // 初始化代价表
74Status DpStGraph::InitCostTable() {
75   // 从配置文件读取s和t的维数
76   uint32_t dim_s = dp_st_speed_config_.matrix_dimension_s();
77   uint32_t  dim_t = dp_st_speed_config_. matrix_dimension_t();
78   // 维数检查,要求大于2
79  DCHECK_GT(dim_s,  2);
80  DCHECK_GT( dim_t2);
81   // 生成代价表cost_table_,列为dim_t,行为dim_s,所有节点均初始化为:StGraphPoint()
82  cost_table_ =  std:: vector< std:: vector >(
83       dim_tstd:: vector (dim_s, StGraphPoint()));
84
85   // cost_table_[i][j] = STPoint(0.0 + j * unit_s_, 0.0 + i * unit_t_);
86   double  curr_t =  0.0;
87   for ( uint32_t i =  0; i < cost_table_.size(); ++i,  curr_t += unit_t_) {
88     auto& cost_table_i = cost_table_[i];
89     double curr_s =  0.0;
90     for ( uint32_t j =  0; j < cost_table_i.size(); ++j, curr_s += unit_s_) {
91      cost_table_i[j].Init(i, j, STPoint(curr_s,  curr_t));
92    }
93  }
94   return Status::OK();
95}
96
97 // 计算总代价
98Status DpStGraph::CalculateTotalCost() {
99   // col and row are for STGraph
100   // t corresponding to col,横坐标
101   // s corresponding to row,纵坐标
102   uint32_t next_highest_row =  0;
103   uint32_t next_lowest_row =  0;
104
105   // 对于第一、二,直至最后一个时间采样值,循环计算不同距离采样值上的代价
106   // 注意:每个时间采样值上的距离采样值数目是不相同的。例如:
107   // 第一个时间采样值为起点,在该点上只能有一个距离采样值:0,否则
108   // 代价表cost_table_就不正确。正常情况下,第二个时间采样值上的距离采样值数目
109   // 会大于1,不然就是匀速前进,玩不下去了^_^
110   for ( size_t c =  0; c < cost_table_.size(); ++c) {
111     // 最高行,即最大加速度情形下所允许的最大距离采样值
112     uint32_t highest_row =  0;
113     // 最低行,即最大减速度情形下所允许的最小距离采样值
114     uint32_t lowest_row = cost_table_.back().size() -  1;
115
116     // 对于时间采样值c上的不同距离采样值r: next_lowest_row<=4<=next_highest_row
117     // 计算抵达节点(c, r)的最小总代价
118     for ( uint32_t r = next_lowest_row; r <= next_highest_row; ++r) {
119       if (FLAGS_enable_multi_thread_in_dp_st_graph) {
120         // 采用线程池方式计算(c, r)的最小总代价
121        PlanningThreadPool::instance()->Push(
122             std::bind(&DpStGraph::CalculateCostAt,  this, c, r));
123      }  else {
124         // 采用单线程方式计算(c, r)的最小总代价
125        CalculateCostAt(c, r);
126      }
127    }
128     // 线程池方式间的同步
129     if (FLAGS_enable_multi_thread_in_dp_st_graph) {
130      PlanningThreadPool::instance()->Synchronize();
131    }
132
133     // 给定时间采样值c的情形下,
134     // 更新最高行(即最大采样距离值)和最低行(即最小采样距离值)
135     for ( uint32_t r = next_lowest_row; r <= next_highest_row; ++r) {
136       const  auto& cost_cr = cost_table_[c][r];
137       if (cost_cr.total_cost() <  std::numeric_limits< double>::infinity()) {
138         uint32_t h_r =  0;
139         uint32_t l_r =  0;
140         // 获取当前节点的最高行和最低行
141        GetRowRange(cost_cr, &h_r, &l_r);
142        highest_row =  std::max(highest_row, h_r);
143        lowest_row =  std::min(lowest_row, l_r);
144      }
145    }
146     // 更新下一次循环的最高行(即最大采样距离)和
147     // 最低行(即最小采样距离)
148    next_highest_row = highest_row;
149    next_lowest_row =  std::max(next_lowest_row, lowest_row);
150  }
151
152   return Status::OK();
153}
154
155 // 基于当前ST图上的点point,获取下一个允许的
156 // 最高行(即最大采样距离)和最低行(即最小采样距离)
157 void DpStGraph::GetRowRange( const StGraphPoint& point,
158                             uint32_t* next_highest_row,
159                             uint32_t* next_lowest_row) {
160   double v0 =  0.0;
161   if (!point.pre_point()) {
162     // 起始点速度
163    v0 = init_point_.v();
164  }  else {
165     // 其他点速度
166    v0 = (point.index_s() - point.pre_point()->index_s()) * unit_s_ / unit_t_;
167  }
168
169   // cost_table_中最后一个时间采样值所包含的距离采样值数目
170   const  size_t max_s_size = cost_table_.back().size() -  1;
171
172   const  double speed_coeff = unit_t_ * unit_t_;
173   // 最大加速度情形下所允许的最大距离
174   const  double delta_s_upper_bound =
175      v0 * unit_t_ + vehicle_param_.max_acceleration() * speed_coeff;
176  *next_highest_row =
177      point.index_s() +  static_cast< uint32_t>(delta_s_upper_bound / unit_s_);
178   if (*next_highest_row >= max_s_size) {
179    *next_highest_row = max_s_size;
180  }
181   // 最大减速度情形下所允许的最小距离
182   const  double delta_s_lower_bound =  std::fmax(
183       0.0, v0 * unit_t_ + vehicle_param_.max_deceleration() * speed_coeff);
184  *next_lowest_row +=  static_cast< int32_t>(delta_s_lower_bound / unit_s_);
185   if (*next_lowest_row > max_s_size) {
186    *next_lowest_row = max_s_size;
187  }
188}
189
190 // 使用动态规划算法计算(c, r)点的总代价
191 // c: 时间坐标,即横坐标
192 // r: 距离坐标,即纵坐标
193 void DpStGraph::CalculateCostAt( const  uint32_t c,  const  uint32_t r) {
194   auto& cost_cr = cost_table_[c][r];
195   // 设置当前点的障碍物代价
196  cost_cr.SetObstacleCost(dp_st_cost_.GetObstacleCost(cost_cr));
197   // 当前点的障碍物代价无穷大,直接返回
198   if (cost_cr.obstacle_cost() >  std::numeric_limits< double>::max()) {
199     return;
200  }
201   // 初始代价
202   const  auto& cost_init = cost_table_[ 0][ 0];
203   // 第一个时间采样值为c(即时间t)== 0,因此r(即距离)必须为0,表示是起点,代价值自然设置为0.0。
204   if (c ==  0) {
205    DCHECK_EQ(r,  0) <<  "Incorrect. Row should be 0 with col = 0. row: " << r;
206    cost_cr.SetTotalCost( 0.0);
207     return;
208  }
209
210   // 获取速度限制条件
211   double speed_limit =
212      st_graph_data_.speed_limit().GetSpeedLimitByS(unit_s_ * r);
213   // 第二个时间采样值
214   if (c ==  1) {
215     // 加速度或减速度超出范围,返回
216     const  double acc = (r * unit_s_ / unit_t_ - init_point_.v()) / unit_t_;
217     if (acc < dp_st_speed_config_.max_deceleration() ||
218        acc > dp_st_speed_config_.max_acceleration()) {
219       return;
220    }
221
222     // 当前点与初始点有重叠,返回
223     if (CheckOverlapOnDpStGraph(st_graph_data_.st_boundaries(), cost_cr,
224                                cost_init)) {
225       return;
226    }
227     // 设置当前点的代价值
228    cost_cr.SetTotalCost(cost_cr.obstacle_cost() + cost_init.total_cost() +
229                         CalculateEdgeCostForSecondCol(r, speed_limit));
230     // 设置其前序节点为起点
231    cost_cr.SetPrePoint(cost_init);
232     return;
233  }
234
235   constexpr  double kSpeedRangeBuffer =  0.20;
236   // 允许的最大距离采样差值
237   const  uint32_t max_s_diff =
238       static_cast< uint32_t>(FLAGS_planning_upper_speed_limit *
239                            ( 1 + kSpeedRangeBuffer) * unit_t_ / unit_s_);
240   // 最小距离采样值
241   const  uint32_t r_low = (max_s_diff < r ? r - max_s_diff :  0);
242   // 上一个时间采样值下不同采样距离的代价数组
243   const  auto& pre_col = cost_table_[c -  1];
244
245   // 第三个时间采样值
246   if (c ==  2) {
247     for ( uint32_t r_pre = r_low; r_pre <= r; ++r_pre) {
248       // 从距离采样值r_pre到r所需的加速度
249       const  double acc =
250          (r * unit_s_ -  2 * r_pre * unit_s_) / (unit_t_ * unit_t_);
251       // 若加速度越界,则忽略该距离采样值
252       if (acc < dp_st_speed_config_.max_deceleration() ||
253          acc > dp_st_speed_config_.max_acceleration()) {
254         continue;
255      }
256       // 与前一时间采样值上的节点有重合,忽略该距离采样值
257       if (CheckOverlapOnDpStGraph(st_graph_data_.st_boundaries(), cost_cr,
258                                  pre_col[r_pre])) {
259         continue;
260      }
261       // 计算当前节点(c, r)的代价值
262       const  double cost = cost_cr.obstacle_cost() +
263                          pre_col[r_pre].total_cost() +
264                          CalculateEdgeCostForThirdCol(r, r_pre, speed_limit);
265       // 若新代价值比节点(c, r)的原有代价值更小,则更新当前节点(c, r)的总代价值
266       if (cost < cost_cr.total_cost()) {
267        cost_cr.SetTotalCost(cost);
268        cost_cr.SetPrePoint(pre_col[r_pre]);
269      }
270    }
271     return;
272  }
273
274   // 其他时间采样值
275   for ( uint32_t r_pre = r_low; r_pre <= r; ++r_pre) {
276
277     // 若节点(c - 1, r_pre)上的总代价无穷大或前一次时间采样c - 1上没有前序节点,忽略该节点    
278     if ( std::isinf(pre_col[r_pre].total_cost()) ||
279        pre_col[r_pre].pre_point() ==  nullptr) {
280       continue;
281    }
282     // 从r_pre抵达r所需的加速度
283     const  double curr_a = (cost_cr.index_s() * unit_s_ +
284                           pre_col[r_pre].pre_point()->index_s() * unit_s_ -
285                            2 * pre_col[r_pre].index_s() * unit_s_) /
286                          (unit_t_ * unit_t_);
287     // 若加速度越界,忽略该距离采样值
288     if (curr_a > vehicle_param_.max_acceleration() ||
289        curr_a < vehicle_param_.max_deceleration()) {
290       continue;
291    }
292     // 与前一时间采样值上的节点有重合,忽略该距离采样值
293     if (CheckOverlapOnDpStGraph(st_graph_data_.st_boundaries(), cost_cr,
294                                pre_col[r_pre])) {
295       continue;
296    }
297
298     // 上上次距离采样值
299     uint32_t r_prepre = pre_col[r_pre].pre_point()->index_s();
300     // ST图上的上上个点
301     const StGraphPoint& prepre_graph_point = cost_table_[c -  2][r_prepre];
302     // 若上上个节点总代价无穷大,忽略之。
303     if ( std::isinf(prepre_graph_point.total_cost())) {
304       continue;
305    }
306     // 若上上个节点没有前序节点,忽略之。
307     if (!prepre_graph_point.pre_point()) {
308       continue;
309    }
310     // 上上上个节点
311     const STPoint& triple_pre_point = prepre_graph_point.pre_point()->point();
312     // 上上个节点
313     const STPoint& prepre_point = prepre_graph_point.point();
314     // 上个节点
315     const STPoint& pre_point = pre_col[r_pre].point();
316     // 当前节点
317     const STPoint& curr_point = cost_cr.point();
318     // 计算从上上上个节点、上上个节点、上个节点与当前节点之间的最小连接代价
319     double cost = cost_cr.obstacle_cost() + pre_col[r_pre].total_cost() +
320                  CalculateEdgeCost(triple_pre_point, prepre_point, pre_point,
321                                    curr_point, speed_limit);
322
323     // 若新代价值比节点(c, r)的原有代价值更小,则更新当前节点(c, r)的总代价值
324     if (cost < cost_cr.total_cost()) {
325      cost_cr.SetTotalCost(cost);
326      cost_cr.SetPrePoint(pre_col[r_pre]);
327    }
328  }
329}
330
331 // 获取代价值最小的速度数据
332Status DpStGraph::RetrieveSpeedProfile(SpeedData*  const speed_data) {
333   // 最小代价值
334   double min_cost =  std::numeric_limits< double>::infinity();
335   // 最优终点(即包含最小代价值的节点)
336   const StGraphPoint* best_end_point =  nullptr;
337   //  cost_table_.back()存储的是最后一个时间采样值上不同距离的代价值
338   for ( const StGraphPoint& cur_point : cost_table_.back()) {
339     if (! std::isinf(cur_point.total_cost()) &&
340        cur_point.total_cost() < min_cost) {
341      best_end_point = &cur_point;
342      min_cost = cur_point.total_cost();
343    }
344  }
345   // 对于cost_table_中的每一行,即第一个、第二个、...、最后一个时间采样值上的
346   // 代价值数组,其最后一个元素存储的是本级时间采样值上的最小代价节点。
347   // 将这些节点与现有最优终点best_end_point比较,
348   // 不断更新最小代价值min_cost和最优终点best_end_point,
349   // 直至找到全局最优终点
350   for ( const  auto& row : cost_table_) {
351     const StGraphPoint& cur_point = row.back();
352     if (! std::isinf(cur_point.total_cost()) &&
353        cur_point.total_cost() < min_cost) {
354      best_end_point = &cur_point;
355      min_cost = cur_point.total_cost();
356    }
357  }
358
359   // 寻找最优终点失败
360   if (best_end_point ==  nullptr) {
361     const  std:: string msg =  "Fail to find the best feasible trajectory.";
362    AERROR << msg;
363     return Status(ErrorCode::PLANNING_ERROR, msg);
364  }
365
366   // 设置最优终点的速度数据,并顺藤摸瓜找出其连接的倒数第二个、倒数第三个直到第一个时间节点
367   // 分别设置这些时间节点的速度数据
368   std:: vector  speed_profile;
369   const StGraphPoint* cur_point = best_end_point;
370   while (cur_point !=  nullptr) {
371    SpeedPoint speed_point;
372    speed_point.set_s(cur_point->point().s());
373    speed_point. set_t(cur_point->point().t());
374    speed_profile.emplace_back(speed_point);
375    cur_point = cur_point->pre_point();
376  }
377   // 将速度数据按起点到终点的顺序重新排列
378   std::reverse(speed_profile.begin(), speed_profile.end());
379
380   // 若速度数据中起始点的时间(t)或距离(s)大于编译器定义的最小双精度浮点数(即起点的时间t
381   // 或距离s不为0),则视结果为错误输出。
382   constexpr  double kEpsilon =  std::numeric_limits< double>::epsilon();
383   if (speed_profile.front().t() > kEpsilon ||
384      speed_profile.front().s() > kEpsilon) {
385     const  std:: string msg =  "Fail to retrieve speed profile.";
386    AERROR << msg;
387     return Status(ErrorCode::PLANNING_ERROR, msg);
388  }
389
390   // 设置最终的速度数据
391  speed_data->set_speed_vector(speed_profile);
392   return Status::OK();
393}
394
395 // 计算第一、二、三、四个节点之间的连接代价
396 double DpStGraph::CalculateEdgeCost( const STPoint& first,  const STPoint& second,
397                                     const STPoint& third,  const STPoint& forth,
398                                     const  double speed_limit) {
399   return dp_st_cost_.GetSpeedCost(third, forth, speed_limit) +
400         dp_st_cost_.GetAccelCostByThreePoints(second, third, forth) +
401         dp_st_cost_.GetJerkCostByFourPoints(first, second, third, forth);
402}
403
404 // 为第二列(即第三个时间采样值上的代价数组)计算连接代价
405 double DpStGraph::CalculateEdgeCostForSecondCol( const  uint32_t row,
406                                                 const  double speed_limit) {
407   double init_speed = init_point_.v();
408   double init_acc = init_point_.a();
409   const STPoint& pre_point = cost_table_[ 0][ 0].point();
410   const STPoint& curr_point = cost_table_[ 1][row].point();
411   return dp_st_cost_.GetSpeedCost(pre_point, curr_point, speed_limit) +
412         dp_st_cost_.GetAccelCostByTwoPoints(init_speed, pre_point,
413                                             curr_point) +
414         dp_st_cost_.GetJerkCostByTwoPoints(init_speed, init_acc, pre_point,
415                                            curr_point);
416}
417
418 // 为第三列(即第四个时间采样值上的代价数组)计算连接代价
419 double DpStGraph::CalculateEdgeCostForThirdCol( const  uint32_t curr_row,
420                                                const  uint32_t pre_row,
421                                                const  double speed_limit) {
422   double init_speed = init_point_.v();
423   const STPoint& first = cost_table_[ 0][ 0].point();
424   const STPoint& second = cost_table_[ 1][pre_row].point();
425   const STPoint& third = cost_table_[ 2][curr_row].point();
426   return dp_st_cost_.GetSpeedCost(second, third, speed_limit) +
427         dp_st_cost_.GetAccelCostByThreePoints(first, second, third) +
428         dp_st_cost_.GetJerkCostByThreePoints(init_speed, first, second, third);
429}

<左右滑动以查看完整代码>


以上是"动态规划及其在Apollo项目Planning模块的应用"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。


开发者说丨动态规划及其在Apollo项目Planning模块的应用



开发者说丨动态规划及其在Apollo项目Planning模块的应用


开发者说丨动态规划及其在Apollo项目Planning模块的应用

* 以上内容为开发者原创,不代表百度官方言论。

内容来自开发者CSDN:



开发者说丨动态规划及其在Apollo项目Planning模块的应用


开发者说丨动态规划及其在Apollo项目Planning模块的应用