Apollo 11.0
自动驾驶开放平台
fem_pos_deviation_sqp_osqp_interface.cc
浏览该文件的文档.
1/******************************************************************************
2 * Copyright 2019 The Apollo Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *****************************************************************************/
16
22
23#include <cmath>
24#include <limits>
25
26#include "cyber/common/log.h"
27
28namespace apollo {
29namespace planning {
30
31using common::math::Vec2d;
32
34 // Sanity Check
35 if (ref_points_.empty()) {
36 AERROR << "reference points empty, solver early terminates";
37 return false;
38 }
39
40 if (ref_points_.size() != bounds_around_refs_.size()) {
41 AERROR << "ref_points and bounds size not equal, solver early terminates";
42 return false;
43 }
44
45 if (ref_points_.size() < 3) {
46 AERROR << "ref_points size smaller than 3, solver early terminates";
47 return false;
48 }
49
50 if (ref_points_.size() > std::numeric_limits<int>::max()) {
51 AERROR << "ref_points size too large, solver early terminates";
52 return false;
53 }
54
55 // Calculate optimization states definitions
56 num_of_points_ = static_cast<int>(ref_points_.size());
57 num_of_pos_variables_ = num_of_points_ * 2;
58 num_of_slack_variables_ = num_of_points_ - 2;
59 num_of_variables_ = num_of_pos_variables_ + num_of_slack_variables_;
60
61 num_of_variable_constraints_ = num_of_variables_;
62 num_of_curvature_constraints_ = num_of_points_ - 2;
63 num_of_constraints_ =
64 num_of_variable_constraints_ + num_of_curvature_constraints_;
65
66 // Set primal warm start
67 std::vector<c_float> primal_warm_start;
68 SetPrimalWarmStart(ref_points_, &primal_warm_start);
69
70 // Calculate kernel
71 std::vector<c_float> P_data;
72 std::vector<c_int> P_indices;
73 std::vector<c_int> P_indptr;
74 CalculateKernel(&P_data, &P_indices, &P_indptr);
75
76 // Calculate offset
77 std::vector<c_float> q;
78 CalculateOffset(ref_points_, &q);
79
80 // Calculate affine constraints
81 std::vector<c_float> A_data;
82 std::vector<c_int> A_indices;
83 std::vector<c_int> A_indptr;
84 std::vector<c_float> lower_bounds;
85 std::vector<c_float> upper_bounds;
86 CalculateAffineConstraint(ref_points_, &A_data, &A_indices, &A_indptr,
87 &lower_bounds, &upper_bounds);
88
89 // Load matrices and vectors into OSQPData
90 OSQPData* data = reinterpret_cast<OSQPData*>(c_malloc(sizeof(OSQPData)));
91 data->n = num_of_variables_;
92 data->m = num_of_constraints_;
93 data->P = csc_matrix(data->n, data->n, P_data.size(), P_data.data(),
94 P_indices.data(), P_indptr.data());
95 data->q = q.data();
96 data->A = csc_matrix(data->m, data->n, A_data.size(), A_data.data(),
97 A_indices.data(), A_indptr.data());
98 data->l = lower_bounds.data();
99 data->u = upper_bounds.data();
100
101 // Define osqp solver settings
102 OSQPSettings* settings =
103 reinterpret_cast<OSQPSettings*>(c_malloc(sizeof(OSQPSettings)));
104 osqp_set_default_settings(settings);
105 settings->max_iter = max_iter_;
106 settings->time_limit = time_limit_;
107 settings->verbose = verbose_;
108 settings->scaled_termination = scaled_termination_;
109 settings->warm_start = warm_start_;
110 settings->polish = true;
111 settings->eps_abs = 1e-5;
112 settings->eps_rel = 1e-5;
113 settings->eps_prim_inf = 1e-5;
114 settings->eps_dual_inf = 1e-5;
115
116 // Define osqp workspace
117 OSQPWorkspace* work = nullptr;
118 // osqp_setup(&work, data, settings);
119 work = osqp_setup(data, settings);
120
121 // Initial solution
122 bool initial_solve_res = OptimizeWithOsqp(primal_warm_start, &work);
123
124 if (!initial_solve_res) {
125 AERROR << "initial iteration solving fails";
126 osqp_cleanup(work);
127 c_free(data->A);
128 c_free(data->P);
129 c_free(data);
130 c_free(settings);
131 return false;
132 }
133
134 // Sequential solution
135
136 int pen_itr = 0;
137 double ctol = 0.0;
138 double original_slack_penalty = weight_curvature_constraint_slack_var_;
139 double last_fvalue = work->info->obj_val;
140
141 while (pen_itr < sqp_pen_max_iter_) {
142 int sub_itr = 1;
143 bool fconverged = false;
144
145 while (sub_itr < sqp_sub_max_iter_) {
146 SetPrimalWarmStart(opt_xy_, &primal_warm_start);
147 CalculateOffset(opt_xy_, &q);
148 CalculateAffineConstraint(opt_xy_, &A_data, &A_indices, &A_indptr,
149 &lower_bounds, &upper_bounds);
150 osqp_update_lin_cost(work, q.data());
151 osqp_update_A(work, A_data.data(), OSQP_NULL, A_data.size());
152 osqp_update_bounds(work, lower_bounds.data(), upper_bounds.data());
153
154 bool iterative_solve_res = OptimizeWithOsqp(primal_warm_start, &work);
155 if (!iterative_solve_res) {
156 AERROR << "iteration at " << sub_itr
157 << ", solving fails with max sub iter " << sqp_sub_max_iter_;
158 weight_curvature_constraint_slack_var_ = original_slack_penalty;
159 osqp_cleanup(work);
160 c_free(data->A);
161 c_free(data->P);
162 c_free(data);
163 c_free(settings);
164 return false;
165 }
166
167 double cur_fvalue = work->info->obj_val;
168 double ftol = std::abs((last_fvalue - cur_fvalue) / last_fvalue);
169
170 if (ftol < sqp_ftol_) {
171 ADEBUG << "merit function value converges at sub itr num " << sub_itr;
172 ADEBUG << "merit function value converges to " << cur_fvalue
173 << ", with ftol " << ftol << ", under max_ftol " << sqp_ftol_;
174 fconverged = true;
175 break;
176 }
177
178 last_fvalue = cur_fvalue;
179 ++sub_itr;
180 }
181
182 if (!fconverged) {
183 AERROR << "Max number of iteration reached";
184 weight_curvature_constraint_slack_var_ = original_slack_penalty;
185 osqp_cleanup(work);
186 c_free(data->A);
187 c_free(data->P);
188 c_free(data);
189 c_free(settings);
190 return false;
191 }
192
193 ctol = CalculateConstraintViolation(opt_xy_);
194
195 ADEBUG << "ctol is " << ctol << ", at pen itr " << pen_itr;
196
197 if (ctol < sqp_ctol_) {
198 ADEBUG << "constraint satisfied at pen itr num " << pen_itr;
199 ADEBUG << "constraint voilation value drops to " << ctol
200 << ", under max_ctol " << sqp_ctol_;
201 weight_curvature_constraint_slack_var_ = original_slack_penalty;
202 osqp_cleanup(work);
203 c_free(data->A);
204 c_free(data->P);
205 c_free(data);
206 c_free(settings);
207 return true;
208 }
209
210 weight_curvature_constraint_slack_var_ *= 10;
211 ++pen_itr;
212 }
213
214 ADEBUG << "constraint not satisfied with total itr num " << pen_itr;
215 ADEBUG << "constraint voilation value drops to " << ctol
216 << ", higher than max_ctol " << sqp_ctol_;
217 weight_curvature_constraint_slack_var_ = original_slack_penalty;
218 osqp_cleanup(work);
219 c_free(data->A);
220 c_free(data->P);
221 c_free(data);
222 c_free(settings);
223 return true;
224}
225
226void FemPosDeviationSqpOsqpInterface::CalculateKernel(
227 std::vector<c_float>* P_data, std::vector<c_int>* P_indices,
228 std::vector<c_int>* P_indptr) {
229 CHECK_GT(num_of_points_, 2);
230
231 // Three quadratic penalties are involved:
232 // 1. Penalty x on distance between middle point and point by finite element
233 // estimate;
234 // 2. Penalty y on path length;
235 // 3. Penalty z on difference between points and reference points
236
237 // General formulation of P matrix is as below(with 6 points as an example):
238 // I is a two by two identity matrix, X, Y, Z represents x * I, y * I, z * I
239 // 0 is a two by two zero matrix
240 // |X+Y+Z, -2X-Y, X, 0, 0, 0, ...|
241 // |0, 5X+2Y+Z, -4X-Y, X, 0, 0, ...|
242 // |0, 0, 6X+2Y+Z, -4X-Y, X, 0, ...|
243 // |0, 0, 0, 6X+2Y+Z, -4X-Y, X ...|
244 // |0, 0, 0, 0, 5X+2Y+Z, -2X-Y ...|
245 // |0, 0, 0, 0, 0, X+Y+Z ...|
246 // |0, 0, 0, 0, 0, 0, 0, ...|
247 // |0, 0, 0, 0, 0, 0, 0, 0, ...|
248 // |0, 0, 0, 0, 0, 0, 0, 0, 0, ...|
249 // |0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...|
250 // Only upper triangle needs to be filled
251 std::vector<std::vector<std::pair<c_int, c_float>>> columns;
252 columns.resize(num_of_variables_);
253 int col_num = 0;
254
255 for (int col = 0; col < 2; ++col) {
256 columns[col].emplace_back(col, weight_fem_pos_deviation_ +
257 weight_path_length_ +
258 weight_ref_deviation_);
259 ++col_num;
260 }
261
262 for (int col = 2; col < 4; ++col) {
263 columns[col].emplace_back(
264 col - 2, -2.0 * weight_fem_pos_deviation_ - weight_path_length_);
265 columns[col].emplace_back(col, 5.0 * weight_fem_pos_deviation_ +
266 2.0 * weight_path_length_ +
267 weight_ref_deviation_);
268 ++col_num;
269 }
270
271 int second_point_from_last_index = num_of_points_ - 2;
272 for (int point_index = 2; point_index < second_point_from_last_index;
273 ++point_index) {
274 int col_index = point_index * 2;
275 for (int col = 0; col < 2; ++col) {
276 col_index += col;
277 columns[col_index].emplace_back(col_index - 4, weight_fem_pos_deviation_);
278 columns[col_index].emplace_back(
279 col_index - 2,
280 -4.0 * weight_fem_pos_deviation_ - weight_path_length_);
281 columns[col_index].emplace_back(
282 col_index, 6.0 * weight_fem_pos_deviation_ +
283 2.0 * weight_path_length_ + weight_ref_deviation_);
284 ++col_num;
285 }
286 }
287
288 int second_point_col_from_last_col = num_of_pos_variables_ - 4;
289 int last_point_col_from_last_col = num_of_pos_variables_ - 2;
290 for (int col = second_point_col_from_last_col;
291 col < last_point_col_from_last_col; ++col) {
292 columns[col].emplace_back(col - 4, weight_fem_pos_deviation_);
293 columns[col].emplace_back(
294 col - 2, -4.0 * weight_fem_pos_deviation_ - weight_path_length_);
295 columns[col].emplace_back(col, 5.0 * weight_fem_pos_deviation_ +
296 2.0 * weight_path_length_ +
297 weight_ref_deviation_);
298 ++col_num;
299 }
300
301 for (int col = last_point_col_from_last_col; col < num_of_pos_variables_;
302 ++col) {
303 columns[col].emplace_back(col - 4, weight_fem_pos_deviation_);
304 columns[col].emplace_back(
305 col - 2, -2.0 * weight_fem_pos_deviation_ - weight_path_length_);
306 columns[col].emplace_back(col, weight_fem_pos_deviation_ +
307 weight_path_length_ +
308 weight_ref_deviation_);
309 ++col_num;
310 }
311
312 CHECK_EQ(col_num, num_of_pos_variables_);
313
314 int ind_p = 0;
315 for (int i = 0; i < num_of_variables_; ++i) {
316 P_indptr->push_back(ind_p);
317 for (const auto& row_data_pair : columns[i]) {
318 // Rescale by 2.0 as the quadratic term in osqp default qp problem setup
319 // is set as (1/2) * x' * P * x
320 P_data->push_back(row_data_pair.second * 2.0);
321 P_indices->push_back(row_data_pair.first);
322 ++ind_p;
323 }
324 }
325 P_indptr->push_back(ind_p);
326}
327
328void FemPosDeviationSqpOsqpInterface::CalculateOffset(
329 std::vector<std::pair<double, double>> points, std::vector<c_float>* q) {
330 q->resize(num_of_variables_);
331 for (int i = 0; i < num_of_points_; ++i) {
332 const auto& ref_point_xy = ref_points_[i];
333 (*q)[2 * i] = -2.0 * weight_ref_deviation_ * ref_point_xy.first;
334 (*q)[2 * i + 1] = -2.0 * weight_ref_deviation_ * ref_point_xy.second;
335 }
336 for (int i = 0; i < num_of_slack_variables_; ++i) {
337 (*q)[num_of_pos_variables_ + i] = weight_curvature_constraint_slack_var_;
338 }
339 AINFO << "FLAGS_enable_obstacle_potential_field: "
340 << FLAGS_enable_obstacle_potential_field
341 << " num_of_points_: " << num_of_points_
342 << "point_box_: " << point_box_.size();
343 if (FLAGS_enable_obstacle_potential_field &&
344 point_box_.size() == num_of_points_ - 1) {
345 AINFO << "use obstacle potential field";
346 for (int i = 1; i < num_of_points_ - 1; ++i) {
347 double grad_x = 0;
348 double grad_y = 0;
349 point_box_[i].push_back(point_box_[i].front());
350 for (int j = 0; j < 4; j++) {
351 Vec2d edge_vec(point_box_[i][j + 1].x() - point_box_[i][j].x(),
352 point_box_[i][j + 1].y() - point_box_[i][j].y());
353 Vec2d ego_vec(points[i].first - point_box_[i][j].x(),
354 points[i].second - point_box_[i][j].y());
355 grad_x += edge_vec.y() / edge_vec.CrossProd(ego_vec);
356 grad_y += -edge_vec.x() / edge_vec.CrossProd(ego_vec);
357 }
358
359 (*q)[2 * i] += FLAGS_sqp_obstacle_weight * grad_x;
360 (*q)[2 * i + 1] += FLAGS_sqp_obstacle_weight * grad_y;
361 }
362 }
363}
364
365std::vector<double>
366FemPosDeviationSqpOsqpInterface::CalculateLinearizedFemPosParams(
367 const std::vector<std::pair<double, double>>& points, const size_t index) {
368 CHECK_GT(index, 0U);
369 CHECK_LT(index, points.size() - 1);
370
371 double x_f = points[index - 1].first;
372 double x_m = points[index].first;
373 double x_l = points[index + 1].first;
374 double y_f = points[index - 1].second;
375 double y_m = points[index].second;
376 double y_l = points[index + 1].second;
377
378 double linear_term_x_f = 2.0 * x_f - 4.0 * x_m + 2.0 * x_l;
379 double linear_term_x_m = 8.0 * x_m - 4.0 * x_f - 4.0 * x_l;
380 double linear_term_x_l = 2.0 * x_l - 4.0 * x_m + 2.0 * x_f;
381 double linear_term_y_f = 2.0 * y_f - 4.0 * y_m + 2.0 * y_l;
382 double linear_term_y_m = 8.0 * y_m - 4.0 * y_f - 4.0 * y_l;
383 double linear_term_y_l = 2.0 * y_l - 4.0 * y_m + 2.0 * y_f;
384
385 double linear_approx = (-2.0 * x_m + x_f + x_l) * (-2.0 * x_m + x_f + x_l) +
386 (-2.0 * y_m + y_f + y_l) * (-2.0 * y_m + y_f + y_l) +
387 -x_f * linear_term_x_f + -x_m * linear_term_x_m +
388 -x_l * linear_term_x_l + -y_f * linear_term_y_f +
389 -y_m * linear_term_y_m + -y_l * linear_term_y_l;
390
391 return {linear_term_x_f, linear_term_y_f, linear_term_x_m, linear_term_y_m,
392 linear_term_x_l, linear_term_y_l, linear_approx};
393}
394
395void FemPosDeviationSqpOsqpInterface::CalculateAffineConstraint(
396 const std::vector<std::pair<double, double>>& points,
397 std::vector<c_float>* A_data, std::vector<c_int>* A_indices,
398 std::vector<c_int>* A_indptr, std::vector<c_float>* lower_bounds,
399 std::vector<c_float>* upper_bounds) {
400 const double scale_factor = 1;
401
402 std::vector<std::vector<double>> lin_cache;
403 for (int i = 1; i < num_of_points_ - 1; ++i) {
404 lin_cache.push_back(CalculateLinearizedFemPosParams(points, i));
405 }
406
407 std::vector<std::vector<std::pair<c_int, c_float>>> columns;
408 columns.resize(num_of_variables_);
409
410 for (int i = 0; i < num_of_variables_; ++i) {
411 columns[i].emplace_back(i, 1.0);
412 }
413
414 for (int i = num_of_pos_variables_; i < num_of_variables_; ++i) {
415 columns[i].emplace_back(i + num_of_slack_variables_, -1.0 * scale_factor);
416 }
417
418 for (int i = 2; i < num_of_points_; ++i) {
419 int index = 2 * i;
420 columns[index].emplace_back(i - 2 + num_of_variables_,
421 lin_cache[i - 2][4] * scale_factor);
422 columns[index + 1].emplace_back(i - 2 + num_of_variables_,
423 lin_cache[i - 2][5] * scale_factor);
424 }
425
426 for (int i = 1; i < num_of_points_ - 1; ++i) {
427 int index = 2 * i;
428 columns[index].emplace_back(i - 1 + num_of_variables_,
429 lin_cache[i - 1][2] * scale_factor);
430 columns[index + 1].emplace_back(i - 1 + num_of_variables_,
431 lin_cache[i - 1][3] * scale_factor);
432 }
433
434 for (int i = 0; i < num_of_points_ - 2; ++i) {
435 int index = 2 * i;
436 columns[index].emplace_back(i + num_of_variables_,
437 lin_cache[i][0] * scale_factor);
438 columns[index + 1].emplace_back(i + num_of_variables_,
439 lin_cache[i][1] * scale_factor);
440 }
441
442 int ind_a = 0;
443 for (int i = 0; i < num_of_variables_; ++i) {
444 A_indptr->push_back(ind_a);
445 for (const auto& row_data_pair : columns[i]) {
446 A_data->push_back(row_data_pair.second);
447 A_indices->push_back(row_data_pair.first);
448 ++ind_a;
449 }
450 }
451 A_indptr->push_back(ind_a);
452
453 lower_bounds->resize(num_of_constraints_);
454 upper_bounds->resize(num_of_constraints_);
455
456 for (int i = 0; i < num_of_points_; ++i) {
457 const auto& ref_point_xy = ref_points_[i];
458 (*upper_bounds)[i * 2] = ref_point_xy.first + bounds_around_refs_[i];
459 (*upper_bounds)[i * 2 + 1] = ref_point_xy.second + bounds_around_refs_[i];
460 (*lower_bounds)[i * 2] = ref_point_xy.first - bounds_around_refs_[i];
461 (*lower_bounds)[i * 2 + 1] = ref_point_xy.second - bounds_around_refs_[i];
462 }
463
464 for (int i = 0; i < num_of_slack_variables_; ++i) {
465 (*upper_bounds)[num_of_pos_variables_ + i] = 1e20;
466 (*lower_bounds)[num_of_pos_variables_ + i] = 0.0;
467 }
468
469 double interval_sqr = average_interval_length_ * average_interval_length_;
470 double curvature_constraint_sqr = (interval_sqr * curvature_constraint_) *
471 (interval_sqr * curvature_constraint_);
472 for (int i = 0; i < num_of_curvature_constraints_; ++i) {
473 (*upper_bounds)[num_of_variable_constraints_ + i] =
474 (curvature_constraint_sqr - lin_cache[i][6]) * scale_factor;
475 (*lower_bounds)[num_of_variable_constraints_ + i] = -1e20;
476 }
477}
478
479void FemPosDeviationSqpOsqpInterface::SetPrimalWarmStart(
480 const std::vector<std::pair<double, double>>& points,
481 std::vector<c_float>* primal_warm_start) {
482 CHECK_EQ(points.size(), static_cast<size_t>(num_of_points_));
483 CHECK_GT(points.size(), 1U);
484
485 // Set states
486 primal_warm_start->resize(num_of_variables_);
487 for (int i = 0; i < num_of_points_; ++i) {
488 (*primal_warm_start)[2 * i] = points[i].first;
489 (*primal_warm_start)[2 * i + 1] = points[i].second;
490 }
491
492 slack_.resize(num_of_slack_variables_);
493 for (int i = 0; i < num_of_slack_variables_; ++i) {
494 (*primal_warm_start)[num_of_pos_variables_ + i] = slack_[i];
495 }
496
497 // Calculate average interval length for curvature constraints
498 double total_length = 0.0;
499 auto pre_point = points.front();
500 for (int i = 1; i < num_of_points_; ++i) {
501 const auto& cur_point = points[i];
502 total_length += std::sqrt((pre_point.first - cur_point.first) *
503 (pre_point.first - cur_point.first) +
504 (pre_point.second - cur_point.second) *
505 (pre_point.second - cur_point.second));
506 pre_point = cur_point;
507 }
508 average_interval_length_ = total_length / (num_of_points_ - 1);
509}
510
511bool FemPosDeviationSqpOsqpInterface::OptimizeWithOsqp(
512 const std::vector<c_float>& primal_warm_start, OSQPWorkspace** work) {
513 osqp_warm_start_x(*work, primal_warm_start.data());
514
515 // Solve Problem
516 osqp_solve(*work);
517
518 auto status = (*work)->info->status_val;
519
520 if (status < 0) {
521 AERROR << "failed optimization status:\t" << (*work)->info->status;
522 return false;
523 }
524
525 if (status != 1 && status != 2) {
526 AERROR << "failed optimization status:\t" << (*work)->info->status;
527 return false;
528 }
529
530 // Extract primal results
531 opt_xy_.resize(num_of_points_);
532 slack_.resize(num_of_slack_variables_);
533 for (int i = 0; i < num_of_points_; ++i) {
534 int index = i * 2;
535 opt_xy_.at(i) = std::make_pair((*work)->solution->x[index],
536 (*work)->solution->x[index + 1]);
537 }
538
539 for (int i = 0; i < num_of_slack_variables_; ++i) {
540 slack_.at(i) = (*work)->solution->x[num_of_pos_variables_ + i];
541 }
542
543 return true;
544}
545
546double FemPosDeviationSqpOsqpInterface::CalculateConstraintViolation(
547 const std::vector<std::pair<double, double>>& points) {
548 CHECK_GT(points.size(), 2U);
549
550 double total_length = 0.0;
551 auto pre_point = points.front();
552 for (int i = 1; i < num_of_points_; ++i) {
553 const auto& cur_point = points[i];
554 total_length += std::sqrt((pre_point.first - cur_point.first) *
555 (pre_point.first - cur_point.first) +
556 (pre_point.second - cur_point.second) *
557 (pre_point.second - cur_point.second));
558 pre_point = cur_point;
559 }
560 double average_interval_length = total_length / (num_of_points_ - 1);
561 double interval_sqr = average_interval_length * average_interval_length;
562 double curvature_constraint_sqr = (interval_sqr * curvature_constraint_) *
563 (interval_sqr * curvature_constraint_);
564
565 double max_cviolation = std::numeric_limits<double>::min();
566 for (size_t i = 1; i < points.size() - 1; ++i) {
567 double x_f = points[i - 1].first;
568 double x_m = points[i].first;
569 double x_l = points[i + 1].first;
570 double y_f = points[i - 1].second;
571 double y_m = points[i].second;
572 double y_l = points[i + 1].second;
573 double cviolation = curvature_constraint_sqr -
574 (-2.0 * x_m + x_f + x_l) * (-2.0 * x_m + x_f + x_l) +
575 (-2.0 * y_m + y_f + y_l) * (-2.0 * y_m + y_f + y_l);
576 max_cviolation = max_cviolation < cviolation ? cviolation : max_cviolation;
577 }
578 return max_cviolation;
579}
580
581} // namespace planning
582} // namespace apollo
Planning module main class.
#define ADEBUG
Definition log.h:41
#define AERROR
Definition log.h:44
#define AINFO
Definition log.h:42
class register implement
Definition arena_queue.h:37