开发者说丨动态规划及其在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
动态规划与分治法(The Divide-and-Conquer Method)有些类似,也是将问题分解为多个子问题,并且基于子问题的结果获得最终解。二者的区别是,分治法将初始问题划分为多个不关联(Disjoint)的子问题(Subproblem)(即子问题相互之间互不依赖),递归地解决子问题,然后将子问题的解合并得到初始问题的解。与之相反,动态规划法分解得到的子问题是相互重叠的(Overlap),即子问题依赖于子子问题(Subsubproblem),子子问题又进一步依赖于下一级的子子问题,这样不断循环直至抵达不需分解的初始条件。在求解过程中,为了避免重复计算子子问题从而提高算法效率,需要将一系列子子问题的解保存到一张表中(table,C++编程一般使用std::array、std::vector或std::list实现),这也就是动态规划又被称为查表计算法的原因。
动态规划一般应用于最优化问题(Optimization Problems)。这类问题一般存在多个解,每个解都具有一个度量值,我们期望得到具有度量值最优(即取最大或最小值)的解。该最优解一般称为最优化问题的一个解。注意,这个解并非唯一,因为最优化问题可能存在多个最优解。
构建一个动态规划算法的一般步骤如下:
1、刻画出一个最优解的结构特征(即使用数学表达式来表述一个最优解);
2、迭代定义一个最优解的度量值;
3、计算每个最优解的度量值,通常采用自下而上的方式;
4、根据计算得到的信息构建出原问题的一个最优解。步骤1-3是使用动态规划求解问题的基础形式。如果我们只需获得最优解的度量值而非最优解本身,则可以忽略步骤4。
Apollo项目Planning模块的EMPlanner中使用动态规划生成代价(Cost)最小的多项式路径(DP路径,见Apollo项目中的DPRoadGraph类)和速度(DP速度,见Apollo项目中的DpStGraph类),DP路径算法和DP速度算法的示意性描述如下图所示(来自百度Apollo项目公开课PPT):
▲图1. DP路径算法
▲图2. DP速度算法
DP路径算法的基本思路是,基于给定的一条中心路径(称为参考线,ReferenceLine)和车道边界条件,每隔一定间隔的纵向距离(称为不同级(Level)上的s值)对道路截面进行均匀采样(与中心点的横向偏移值称为为l值),这样会得到图中黑点所示的采样点(这些采样点称为航点,Waypoint)数组。基于一定的规则,可以给出各航点迁移的代价值(Cost)。航点迁移不一定需要逐级进行,可以从第一级跳到第三级甚至终点,只要迁移代价值最小化即可,这显然满足动态规划的求解思路。
DP速度算法的基本思路是,在DP路径算法生成一条可行驶的路径后,从起点开始,考虑避开路径中的所有障碍物,并且让加减速最为平顺,以最优的速度曲线(即t-s平面中的绿色曲线)安全抵达终点,这也可以使用动态规划的思路求解。
航点数组生成
1// 采样获得路径航点
2bool DPRoadGraph::SamplePathWaypoints(
3 const common::TrajectoryPoint &init_point,
4 std::vector<std::vector<:slpoint>> *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<:slpoint> 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<:slpoint>> 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<:slpoint>{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<:slpoint> > 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.0, 0.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.0, 0.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}
<左右滑动以查看完整代码>
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.0, 0.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_t, 2);
81 // 生成代价表cost_table_,列为dim_t,行为dim_s,所有节点均初始化为:StGraphPoint()
82 cost_table_ = std::vector<std::vector>(
83 dim_t, std::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// 最高行(即最大采样距离)和最低行(即最小采样距离)
157void 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: 距离坐标,即纵坐标
193void 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// 计算第一、二、三、四个节点之间的连接代价
396double 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// 为第二列(即第三个时间采样值上的代价数组)计算连接代价
405double 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// 为第三列(即第四个时间采样值上的代价数组)计算连接代价
419double 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小哥哥』为好友,进开发者交流群。
* 以上内容为开发者原创,不代表百度官方言论。
内容来自开发者CSDN:
https://blog.csdn.net/davidhopper/article/details/79399640,欢迎大家收藏点赞。已获开发者授权,原文地址请戳阅读原文。