Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
smoothing.cpp
Go to the documentation of this file.
2
3std::vector<PathPoint> PathSmoothing::smooth_path(const std::vector<PathPoint>& path,
4 bool is_path_closed) const {
6 return path;
7 }
8 std::vector<PathPoint> result_path = filter_path(::fit_spline(
10
11 return result_path;
12}
13
14std::vector<PathPoint> PathSmoothing::optimize_path(
15 const std::vector<PathPoint>& path, const std::vector<PathPoint>& yellow_cones,
16 const std::vector<PathPoint>& blue_cones) const {
18 return smooth_path(path,true);
19 }
20
21 auto splines = ::fit_triple_spline(path, blue_cones, yellow_cones, config_.spline_precision_,
23
24 const std::vector<PathPoint> optimize_path =
25 osqp_optimization(splines.center, splines.left, splines.right);
26 std::vector<PathPoint> filtered_path = filter_path(optimize_path);
27
28 return filtered_path;
29}
30
31std::vector<PathPoint> PathSmoothing::filter_path(const std::vector<PathPoint>& path) const {
32 std::vector<PathPoint> filtered;
33 for (const auto& p : path) {
34 if (filtered.empty() || filtered.back().position.euclidean_distance(p.position) >
36 filtered.push_back(p);
37 }
38 }
39 return filtered;
40}
41
43 int num_path_points, const std::function<int(int)>& circular_index,
44 const std::function<void(int, int, double)>& add_coefficient) const {
45 // -------- ADD CURVATURE PENALTY TERMS --------
46 // Penalize second-order differences to minimize curvature
47 // For each point, we penalize: (p[i-1] - 2*p[i] + p[i+1])^2
48 for (int point_idx = 0; point_idx < num_path_points; ++point_idx) {
49 int prev_point = circular_index(point_idx - 1);
50 int next_point = circular_index(point_idx + 1);
51
52 // X-coordinate curvature terms
53 int x_prev = 2 * prev_point;
54 int x_current = 2 * point_idx;
55 int x_next = 2 * next_point;
56
57 add_coefficient(x_prev, x_prev, config_.curvature_weight_);
58 add_coefficient(x_current, x_current, 4 * config_.curvature_weight_);
59 add_coefficient(x_next, x_next, config_.curvature_weight_);
60 add_coefficient(x_prev, x_current, -2 * config_.curvature_weight_);
61 add_coefficient(x_current, x_next, -2 * config_.curvature_weight_);
62 add_coefficient(x_prev, x_next, config_.curvature_weight_);
63
64 // Y-coordinate curvature terms
65 int y_prev = 2 * prev_point + 1;
66 int y_current = 2 * point_idx + 1;
67 int y_next = 2 * next_point + 1;
68
69 add_coefficient(y_prev, y_prev, config_.curvature_weight_);
70 add_coefficient(y_current, y_current, 4 * config_.curvature_weight_);
71 add_coefficient(y_next, y_next, config_.curvature_weight_);
72 add_coefficient(y_prev, y_current, -2 * config_.curvature_weight_);
73 add_coefficient(y_current, y_next, -2 * config_.curvature_weight_);
74 add_coefficient(y_prev, y_next, config_.curvature_weight_);
75 }
76}
77
79 int num_path_points, const std::function<void(int, int, double)>& add_coefficient) const {
80 // -------- ADD SLACK VARIABLE PENALTY TERMS --------
81 // Penalize slack variables to encourage staying within bounds
82 const int num_slack_variables = 2 * num_path_points;
83 for (int slack_idx = 0; slack_idx < num_slack_variables; ++slack_idx) {
84 int slack_variable_index = 2 * num_path_points + slack_idx;
85 add_coefficient(slack_variable_index, slack_variable_index, config_.safety_weight_);
86 }
87}
88
90 std::vector<OSQPFloat>& constraint_values, std::vector<OSQPInt>& constraint_row_indices,
91 std::vector<OSQPInt>& constraint_col_indices, std::vector<OSQPFloat>& constraint_lower_bounds,
92 std::vector<OSQPFloat>& constraint_upper_bounds, int& constraint_count,
93 const std::vector<PathPoint>& left, const std::vector<PathPoint>& right, int num_path_points,
94 double safety_margin) const {
95 // -------- ADD TRACK BOUNDARY CONSTRAINTS --------
96 // For each point, ensure it stays within the left and right boundaries
97 for (int point_idx = 0; point_idx < num_path_points; ++point_idx) {
98 Eigen::Vector2d left_boundary_point(left[point_idx].position.x, left[point_idx].position.y);
99 Eigen::Vector2d right_boundary_point(right[point_idx].position.x, right[point_idx].position.y);
100
101 // Compute lateral direction (perpendicular to track)
102 Eigen::Vector2d lateral_direction = (left_boundary_point - right_boundary_point).normalized();
103
104 // Right boundary constraint: lateral_direction · point + slack >= right_boundary_value
105 constraint_row_indices.push_back(constraint_count);
106 constraint_col_indices.push_back(2 * point_idx);
107 constraint_values.push_back(lateral_direction.x());
108
109 constraint_row_indices.push_back(constraint_count);
110 constraint_col_indices.push_back(2 * point_idx + 1);
111 constraint_values.push_back(lateral_direction.y());
112
113 constraint_row_indices.push_back(constraint_count);
114 constraint_col_indices.push_back(2 * num_path_points + 2 * point_idx);
115 constraint_values.push_back(1.0);
116
117 double right_boundary_constraint = right_boundary_point.dot(lateral_direction) + safety_margin;
118 constraint_lower_bounds.push_back(right_boundary_constraint);
119 constraint_upper_bounds.push_back(OSQP_INFTY);
120 constraint_count++;
121
122 // Left boundary constraint: -lateral_direction · point + slack >= -left_boundary_value
123 constraint_row_indices.push_back(constraint_count);
124 constraint_col_indices.push_back(2 * point_idx);
125 constraint_values.push_back(-lateral_direction.x());
126
127 constraint_row_indices.push_back(constraint_count);
128 constraint_col_indices.push_back(2 * point_idx + 1);
129 constraint_values.push_back(-lateral_direction.y());
130
131 constraint_row_indices.push_back(constraint_count);
132 constraint_col_indices.push_back(2 * num_path_points + 2 * point_idx + 1);
133 constraint_values.push_back(1.0);
134
135 double left_boundary_constraint = -left_boundary_point.dot(lateral_direction) + safety_margin;
136 constraint_lower_bounds.push_back(left_boundary_constraint);
137 constraint_upper_bounds.push_back(OSQP_INFTY);
138 constraint_count++;
139 }
140}
141
143 std::vector<OSQPFloat>& constraint_values, std::vector<OSQPInt>& constraint_row_indices,
144 std::vector<OSQPInt>& constraint_col_indices, std::vector<OSQPFloat>& constraint_lower_bounds,
145 std::vector<OSQPFloat>& constraint_upper_bounds, int& constraint_count,
146 int num_path_points) const {
147 // -------- ADD SLACK VARIABLE NON-NEGATIVITY CONSTRAINTS --------
148 // Ensure all slack variables are non-negative
149 const int num_slack_variables = 2 * num_path_points;
150 for (int slack_idx = 0; slack_idx < num_slack_variables; ++slack_idx) {
151 constraint_row_indices.push_back(constraint_count);
152 constraint_col_indices.push_back(2 * num_path_points + slack_idx);
153 constraint_values.push_back(1.0);
154
155 constraint_lower_bounds.push_back(0.0);
156 constraint_upper_bounds.push_back(OSQP_INFTY);
157 constraint_count++;
158 }
159}
160
161void PathSmoothing::convert_to_csc_format(const std::vector<OSQPFloat>& values,
162 const std::vector<OSQPInt>& row_indices,
163 const std::vector<OSQPInt>& col_indices,
164 int total_variables, std::vector<OSQPFloat>& csc_x,
165 std::vector<OSQPInt>& csc_i,
166 std::vector<OSQPInt>& csc_p) const {
167 // Convert coordinate format to CSC (Compressed Sparse Column) format
168 std::vector<std::vector<std::pair<OSQPInt, OSQPFloat>>> columns(total_variables);
169 for (size_t entry = 0; entry < values.size(); ++entry) {
170 columns[col_indices[entry]].push_back({row_indices[entry], values[entry]});
171 }
172
173 // Sort each column by row index
174 for (int col = 0; col < total_variables; ++col) {
175 std::sort(columns[col].begin(), columns[col].end(),
176 [](const auto& a, const auto& b) { return a.first < b.first; });
177 }
178
179 // Build CSC arrays
180 csc_x.reserve(values.size());
181 csc_i.reserve(values.size());
182 csc_p.resize(total_variables + 1);
183
184 csc_p[0] = 0;
185 for (int col = 0; col < total_variables; ++col) {
186 for (const auto& [row, value] : columns[col]) {
187 csc_i.push_back(row);
188 csc_x.push_back(value);
189 }
190 csc_p[col + 1] = csc_x.size();
191 }
192}
193
194std::vector<PathPoint> PathSmoothing::osqp_optimization(const std::vector<PathPoint>& center,
195 const std::vector<PathPoint>& left,
196 const std::vector<PathPoint>& right) const {
197 if (center.size() != left.size() || center.size() != right.size() ||
198 left.size() != center.size()) {
199 RCLCPP_INFO(rclcpp::get_logger("rclcpp"),
200 "The splines have different sizes. Right - %ld, Left - %ld, Center - %ld",
201 center.size(), left.size(), right.size());
202 }
203 const int num_path_points = center.size();
204
205 // Check if we have enough points for optimization
206 if (num_path_points < 5) {
207 RCLCPP_INFO(rclcpp::get_logger("rclcpp"),
208 "Too few points for OSQP optimization (%d points). Minimum is 5.", num_path_points);
209 return center;
210 }
211
212 // -------- COMPUTE SAFETY MARGIN --------
213 const double safety_margin = config_.car_width_ / 2 + config_.safety_margin_;
214
215 // Helper lambda for circular indexing (wraps around the path)
216 auto circular_index = [&](int i) { return (i + num_path_points) % num_path_points; };
217
218 // -------- DEFINE OPTIMIZATION VARIABLES --------
219 // Decision variables: 2 coordinates (x,y) per point + 2 slack variables per point
220 const int num_slack_variables = 2 * num_path_points;
221 const int total_variables = 2 * num_path_points + num_slack_variables;
222
223 // -------- BUILD QUADRATIC OBJECTIVE MATRIX (P) --------
224 // Store quadratic terms in a map for easy accumulation, then convert to sparse format
225 std::map<std::pair<int, int>, double> quadratic_terms;
226
227 // Helper lambda to add coefficients to the upper triangular part of P
228 auto add_quadratic_coefficient = [&](int row_idx, int col_idx, double coefficient) {
229 if (row_idx > col_idx) {
230 std::swap(row_idx, col_idx);
231 }
232 quadratic_terms[{row_idx, col_idx}] += coefficient;
233 };
234
235 add_curvature_terms(num_path_points, circular_index, add_quadratic_coefficient);
236 add_slack_penalty_terms(num_path_points, add_quadratic_coefficient);
237
238 // -------- BUILD LINEAR OBJECTIVE VECTOR (q) --------
239 // All zeros since we only have quadratic terms in the objective
240 std::vector<OSQPFloat> linear_objective(total_variables, 0.0);
241
242 // -------- CONVERT QUADRATIC TERMS TO SPARSE FORMAT --------
243 std::vector<OSQPFloat> P_values;
244 std::vector<OSQPInt> P_row_indices, P_col_indices;
245
246 P_values.reserve(quadratic_terms.size());
247 P_row_indices.reserve(quadratic_terms.size());
248 P_col_indices.reserve(quadratic_terms.size());
249
250 for (const auto& [indices, value] : quadratic_terms) {
251 P_row_indices.push_back(indices.first);
252 P_col_indices.push_back(indices.second);
253 P_values.push_back(value);
254 }
255
256 // -------- BUILD CONSTRAINT MATRIX (A) AND BOUNDS --------
257 std::vector<OSQPFloat> constraint_values;
258 std::vector<OSQPInt> constraint_row_indices, constraint_col_indices;
259 std::vector<OSQPFloat> constraint_lower_bounds, constraint_upper_bounds;
260
261 int constraint_count = 0;
262
263 add_boundary_constraints(constraint_values, constraint_row_indices, constraint_col_indices,
264 constraint_lower_bounds, constraint_upper_bounds, constraint_count, left,
265 right, num_path_points, safety_margin);
266
267 add_slack_nonnegativity_constraints(constraint_values, constraint_row_indices,
268 constraint_col_indices, constraint_lower_bounds,
269 constraint_upper_bounds, constraint_count, num_path_points);
270
271 const int total_constraints = constraint_count;
272
273 // Build CSC arrays for P
274 std::vector<OSQPFloat> P_x;
275 std::vector<OSQPInt> P_i;
276 std::vector<OSQPInt> P_p;
277 convert_to_csc_format(P_values, P_row_indices, P_col_indices, total_variables, P_x, P_i, P_p);
278
279 // Build CSC arrays for A
280 std::vector<OSQPFloat> A_x;
281 std::vector<OSQPInt> A_i;
282 std::vector<OSQPInt> A_p;
283 convert_to_csc_format(constraint_values, constraint_row_indices, constraint_col_indices,
284 total_variables, A_x, A_i, A_p);
285
286 // -------- POPULATE MATRIX STRUCTURES --------
287 OSQPCscMatrix objective_matrix;
288 objective_matrix.m = total_variables;
289 objective_matrix.n = total_variables;
290 objective_matrix.nzmax = P_x.size();
291 objective_matrix.nz = -1;
292 objective_matrix.x = P_x.data();
293 objective_matrix.i = P_i.data();
294 objective_matrix.p = P_p.data();
295
296 OSQPCscMatrix constraint_matrix;
297 constraint_matrix.m = total_constraints;
298 constraint_matrix.n = total_variables;
299 constraint_matrix.nzmax = A_x.size();
300 constraint_matrix.nz = -1;
301 constraint_matrix.x = A_x.data();
302 constraint_matrix.i = A_i.data();
303 constraint_matrix.p = A_p.data();
304
305 // -------- CONFIGURE OSQP SOLVER SETTINGS --------
306 OSQPSettings solver_settings;
307 ::osqp_set_default_settings(&solver_settings);
308 solver_settings.verbose = 1;
309 solver_settings.max_iter = config_.max_iterations_;
310 solver_settings.eps_abs = config_.tolerance_;
311 solver_settings.eps_rel = config_.tolerance_;
312 solver_settings.polishing = 1;
313
314 // -------- SETUP OSQP SOLVER --------
315 OSQPSolver* solver = nullptr;
316
317 OSQPInt setup_status =
318 ::osqp_setup(&solver, &objective_matrix, linear_objective.data(), &constraint_matrix,
319 constraint_lower_bounds.data(), constraint_upper_bounds.data(), total_variables,
320 total_constraints, &solver_settings);
321
322 if (setup_status != 0) {
323 RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "OSQP setup failed with status %lld", setup_status);
324 if (solver) {
325 (void)::osqp_cleanup(solver);
326 }
327 return center;
328 }
329
330 // -------- SOLVE THE OPTIMIZATION PROBLEM --------
331 OSQPInt solve_status = ::osqp_solve(solver);
332
333 // -------- EXTRACT OPTIMIZED PATH FROM SOLUTION --------
334 std::vector<PathPoint> optimized_path = center;
335
336 if ((solver->solution != nullptr) && (solver->solution->x != nullptr)) {
337 for (int point_idx = 0; point_idx < num_path_points; ++point_idx) {
338 optimized_path[point_idx].position.x = solver->solution->x[2 * point_idx];
339 optimized_path[point_idx].position.y = solver->solution->x[2 * point_idx + 1];
340 }
341
342 RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"),
343 "OSQP optimization completed successfully with %d points (status: %lld)",
344 num_path_points, solve_status);
345 } else {
346 RCLCPP_WARN(rclcpp::get_logger("rclcpp"), "OSQP solution is null, returning original path");
347 }
348
349 // -------- CLEANUP OSQP RESOURCES --------
350 (void)::osqp_cleanup(solver);
351
352 return optimized_path;
353}
std::vector< PathPoint > smooth_path(const std::vector< PathPoint > &path, bool is_path_closed) const
Smooths a path by fitting a B-spline through the input points.
Definition smoothing.cpp:3
void convert_to_csc_format(const std::vector< OSQPFloat > &values, const std::vector< OSQPInt > &row_indices, const std::vector< OSQPInt > &col_indices, int total_variables, std::vector< OSQPFloat > &csc_x, std::vector< OSQPInt > &csc_i, std::vector< OSQPInt > &csc_p) const
Converts sparse matrix data from coordinate format to Compressed Sparse Column (CSC) format.
std::vector< PathPoint > osqp_optimization(const std::vector< PathPoint > &center, const std::vector< PathPoint > &left, const std::vector< PathPoint > &right) const
Optimizes a path using quadratic programming (OSQP) to balance smoothness, curvature,...
void add_curvature_terms(int num_path_points, const std::function< int(int)> &circular_index, const std::function< void(int, int, double)> &add_coefficient) const
Adds curvature penalty terms to the quadratic objective function.
Definition smoothing.cpp:42
std::vector< PathPoint > filter_path(const std::vector< PathPoint > &path) const
Filters path points using a minimum spacing constraint.
Definition smoothing.cpp:31
PathSmoothingConfig config_
configuration of the smoothing algorithm
Definition smoothing.hpp:65
void add_slack_nonnegativity_constraints(std::vector< OSQPFloat > &constraint_values, std::vector< OSQPInt > &constraint_row_indices, std::vector< OSQPInt > &constraint_col_indices, std::vector< OSQPFloat > &constraint_lower_bounds, std::vector< OSQPFloat > &constraint_upper_bounds, int &constraint_count, int num_path_points) const
Adds non-negativity constraints for slack variables.
void add_boundary_constraints(std::vector< OSQPFloat > &constraint_values, std::vector< OSQPInt > &constraint_row_indices, std::vector< OSQPInt > &constraint_col_indices, std::vector< OSQPFloat > &constraint_lower_bounds, std::vector< OSQPFloat > &constraint_upper_bounds, int &constraint_count, const std::vector< PathPoint > &left, const std::vector< PathPoint > &right, int num_path_points, double safety_margin) const
Adds track boundary constraints to ensure the optimized path stays within the track.
Definition smoothing.cpp:89
void add_slack_penalty_terms(int num_path_points, const std::function< void(int, int, double)> &add_coefficient) const
Adds penalty terms for slack variables to the quadratic objective function.
Definition smoothing.cpp:78
std::vector< PathPoint > optimize_path(const std::vector< PathPoint > &path, const std::vector< PathPoint > &yellow_cones, const std::vector< PathPoint > &blue_cones) const
Optimizes a racing line path by fitting splines through track boundaries and applying quadratic progr...
Definition smoothing.cpp:14
TripleSpline< T > fit_triple_spline(const std::vector< T > &center, const std::vector< T > &left, const std::vector< T > &right, int precision, int order)
This function takes three sequences of points (center, left, right), fits splines to them using GSL i...
Definition splines.hpp:315
std::vector< T > fit_spline(const std::vector< T > &path, int precision, int order, float coeffs_ratio)
This function takes a sequence of points (T), fits a spline to them using B-spline basis functions,...
Definition splines.hpp:130
double curvature_weight_
Weight for curvature minimization in the optimization cost function.
double car_width_
Vehicle width in meters.
double safety_weight_
Weight for safety distance from obstacles in the optimization cost function.
double safety_margin_
Additional safety clearance in meters.
double tolerance_
Convergence tolerance for the optimization solver.
bool use_optimization_
Flag to enable/disable optimization-based refinement after spline fitting.
float spline_coeffs_ratio_
Ratio between number of spline coefficients and input points.
int spline_order_
Order of the B-spline used for smoothing.
int max_iterations_
Maximum number of iterations for the optimization solver.
bool use_path_smoothing_
Flag to enable/disable spline-based path smoothing.
float min_path_point_distance_
Minimum distance between consecutive path points after smoothing.
int spline_precision_
Number of interpolated points between each pair of input points.