Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
splines.hpp
Go to the documentation of this file.
1#ifndef SRC_PLANNING_INCLUDE_UTILS_SPLINES_HPP_
2#define SRC_PLANNING_INCLUDE_UTILS_SPLINES_HPP_
3
4#include <gsl/gsl_bspline.h>
5#include <gsl/gsl_errno.h>
6#include <gsl/gsl_interp.h>
7#include <gsl/gsl_multifit.h>
8#include <gsl/gsl_spline.h>
9
10#include <cmath>
11#include <type_traits>
12#include <unordered_set>
13#include <utility>
14#include <vector>
15
16#include "rclcpp/rclcpp.hpp"
17
37// Check for default constructor
38template <typename T, typename = void>
39struct HasDefaultConstructor : public std::false_type {};
40
41template <typename T>
42struct HasDefaultConstructor<T, std::void_t<decltype(T())>> : public std::true_type {};
43
44// Check for hash function
45template <typename T, typename = void>
46struct IsHashable : public std::false_type {};
47
48template <typename T>
49struct IsHashable<T, std::void_t<decltype(std::hash<T>{}(std::declval<T>()))>>
50 : public std::true_type {};
51
52// Check for operator==
53template <typename T, typename = void>
54struct HasEqualityOperator : public std::false_type {};
55
56template <typename T>
57struct HasEqualityOperator<T, std::void_t<decltype(std::declval<T>() == std::declval<T>())>>
58 : public std::true_type {};
59
60// Check for position member
61template <typename T, typename = void>
62struct HasPosition : public std::false_type {};
63
64template <typename T>
65struct HasPosition<T, std::void_t<decltype(std::declval<T>().position)>> : public std::true_type {};
66
67// Check for position.x and position.y members
68template <typename T, typename = void>
69struct HasPositionXY : public std::false_type {};
70
71template <typename T>
73 T, std::void_t<decltype(std::declval<T>().position.x), decltype(std::declval<T>().position.y)>>
74 : public std::true_type {};
75
76// Check for copy constructor
77template <typename T, typename = void>
78struct IsCopyConstructor : public std::false_type {};
79
80template <typename T>
81struct IsCopyConstructor<T, std::void_t<decltype(T(std::declval<T>()))>> : public std::true_type {};
82
83// Check for euclidean_distance method
84template <typename T, typename = void>
85struct HasEuclideanDistance : public std::false_type {};
86
87template <typename T>
88struct HasEuclideanDistance<T, std::void_t<decltype(std::declval<T>().position.euclidean_distance(
89 std::declval<T>().position))>> : public std::true_type {};
90
91// Check for position.x and position.y being double
92template <typename T, typename = void>
93struct PositionXYAreDouble : public std::false_type {};
94
95template <typename T>
97 std::vector<T> center;
98 std::vector<T> left;
99 std::vector<T> right;
100};
101
102template <typename T>
104 T, std::enable_if_t<std::is_same_v<decltype(std::declval<T>().position.x), double> &&
105 std::is_same_v<decltype(std::declval<T>().position.y), double>>>
106 : public std::true_type {};
107
129template <typename T>
130std::vector<T> fit_spline(const std::vector<T> &path, int precision, int order,
131 float coeffs_ratio) {
132 static_assert(HasDefaultConstructor<T>::value, "T must be default constructible");
133 static_assert(IsHashable<T>::value, "T must be hashable");
134 static_assert(HasEqualityOperator<T>::value, "T must have operator==");
135 static_assert(HasPosition<T>::value, "T must have a position member");
136 static_assert(HasPositionXY<T>::value, "T.position must have x and y members");
137 static_assert(IsCopyConstructor<T>::value, "T must be copyable");
138 static_assert(HasEuclideanDistance<T>::value, "T.position must have a euclidean_distance method");
139 static_assert(PositionXYAreDouble<T>::value, "T.position.x and T.position.y must be double");
140
141 size_t n = path.size();
142 if (n < 2) {
143 return path;
144 }
145
146 // Garantee ncoeffs >= order -> mathematical requirement for B-splines
147 size_t ncoeffs = static_cast<size_t>(static_cast<float>(n) / coeffs_ratio);
148 if (ncoeffs < static_cast<size_t>(order)) {
149 return path;
150 }
151
152 if (ncoeffs > n) {
153 ncoeffs = n;
154 }
155
156 // Number of breakpoints in the B-spline (knots)
157 const int nbreak = static_cast<int>(ncoeffs) - order + 2;
158
159 if (nbreak < 2 || n == 0) {
160 RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"),
161 "Too few points to calculate spline while executing 'fit_spline'"
162 "Number of cones was %i",
163 static_cast<int>(n));
164 return path;
165 }
166 // -------- INITIALIZE GSL WORKSPACES --------
167 gsl_bspline_workspace *bw =
168 ::gsl_bspline_alloc(static_cast<size_t>(order), static_cast<size_t>(nbreak));
169 gsl_bspline_workspace *cw = ::gsl_bspline_alloc(order, nbreak);
170
171 gsl_vector *B = ::gsl_vector_alloc(ncoeffs);
172 gsl_vector *C = ::gsl_vector_alloc(ncoeffs);
173 gsl_vector *c = ::gsl_vector_alloc(ncoeffs);
174 gsl_vector *c2 = ::gsl_vector_alloc(ncoeffs);
175
176 gsl_vector *w = ::gsl_vector_alloc(n);
177 gsl_vector *x_values = ::gsl_vector_alloc(n);
178 gsl_vector *y_values = ::gsl_vector_alloc(n);
179 gsl_vector *i_values = ::gsl_vector_alloc(n);
180
181 gsl_matrix *X = ::gsl_matrix_alloc(n, ncoeffs);
182 gsl_matrix *Y = ::gsl_matrix_alloc(n, ncoeffs);
183
184 gsl_matrix *cov = ::gsl_matrix_alloc(ncoeffs, ncoeffs);
185 gsl_matrix *cov2 = ::gsl_matrix_alloc(ncoeffs, ncoeffs);
186
187 gsl_multifit_linear_workspace *mw = ::gsl_multifit_linear_alloc(n, ncoeffs);
188 gsl_multifit_linear_workspace *mw2 = ::gsl_multifit_linear_alloc(n, ncoeffs);
189
190 double chisq = 0.0;
191 double chisq2 = 0.0;
192
193 // -------- FILL INPUT DATA AND WEIGHTS --------
194 for (size_t i = 0; i < n; i++) {
195 ::gsl_vector_set(i_values, i, static_cast<double>(i));
196 ::gsl_vector_set(x_values, i, path[i].position.x);
197 ::gsl_vector_set(y_values, i, path[i].position.y);
198
199 // Weight points based on local spacing (closer points get larger weight)
200 double dist_next = 1.0;
201 if (i + 1 < n) {
202 dist_next = path[i].position.euclidean_distance(path[i + 1].position);
203 } else if (i > 0) {
204 dist_next = path[i].position.euclidean_distance(path[i - 1].position);
205 }
206
207 if (dist_next < 1e-6) {
208 dist_next = 1e-6;
209 }
210
211 ::gsl_vector_set(w, i, 1.0 / (dist_next * dist_next));
212 }
213
214 // -------- SET UNIFORM KNOTS FOR BOTH SPLINES --------
215 double t_min = 0.0;
216 double t_max = static_cast<double>(n - 1);
217 (void)::gsl_bspline_knots_uniform(t_min, t_max, bw);
218 (void)::gsl_bspline_knots_uniform(t_min, t_max, cw);
219
220 // -------- BUILD DESIGN MATRICES --------
221 // Evaluate each basis function at each input index
222 for (int i = 0; i < static_cast<int>(n); i++) {
223 (void)::gsl_bspline_eval(i, B, bw);
224 (void)::gsl_bspline_eval(i, C, cw);
225
226 // Fill design matrices for x and y least squares
227 for (int j = 0; j < static_cast<int>(ncoeffs); j++) {
228 ::gsl_matrix_set(X, i, j, ::gsl_vector_get(B, j));
229 ::gsl_matrix_set(Y, i, j, ::gsl_vector_get(C, j));
230 }
231 }
232
233 // -------- LEAST SQUARES FIT FOR X AND Y --------
234 (void)::gsl_multifit_wlinear(X, w, x_values, c, cov, &chisq, mw);
235 (void)::gsl_multifit_wlinear(Y, w, y_values, c2, cov2, &chisq2, mw2);
236
237 // -------- EVALUATE THE SPLINE --------
238 std::vector<T> path_eval;
239 path_eval.reserve(n * precision);
240 for (int i = 0; i < static_cast<int>(n)-1; i++) {
241 for (int j = 0; j < precision; j++) {
242 // Compute parameter t inside segment i
243 double t = static_cast<double>(i) + static_cast<double>(j) / precision;
244 if (t > t_max) {
245 t = t_max;
246 }
247
248 (void)::gsl_bspline_eval(t, B, bw);
249 (void)::gsl_bspline_eval(t, C, cw);
250
251 // Compute x and y via estimated spline coefficients
252 double xi = 0.0;
253 double yi = 0.0;
254 double err = 0.0;
255 (void)::gsl_multifit_linear_est(B, c, cov, &xi, &err);
256 (void)::gsl_multifit_linear_est(C, c2, cov2, &yi, &err);
257
258 // Create new spline point
259 T new_element;
260 new_element.position.x = xi;
261 new_element.position.y = yi;
262 path_eval.push_back(new_element);
263 }
264 }
265
266 // -------- CLEANUP GSL RESOURCES --------
267 ::gsl_bspline_free(bw);
268 ::gsl_bspline_free(cw);
269 ::gsl_vector_free(B);
270 ::gsl_vector_free(C);
271 ::gsl_vector_free(c);
272 ::gsl_vector_free(c2);
273 ::gsl_vector_free(w);
274 ::gsl_vector_free(x_values);
275 ::gsl_vector_free(y_values);
276 ::gsl_vector_free(i_values);
277 ::gsl_matrix_free(X);
278 ::gsl_matrix_free(Y);
279 ::gsl_matrix_free(cov);
280 ::gsl_matrix_free(cov2);
281 ::gsl_multifit_linear_free(mw);
282 ::gsl_multifit_linear_free(mw2);
283
284 RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"), "END fitSpline with %i points",
285 static_cast<int>(path_eval.size()));
286
287 return path_eval;
288}
314template <typename T>
315TripleSpline<T> fit_triple_spline(const std::vector<T> &center, const std::vector<T> &left,
316 const std::vector<T> &right, int precision, int order) {
317 static_assert(HasDefaultConstructor<T>::value, "T must be default constructible");
318 static_assert(IsHashable<T>::value, "T must be hashable");
319 static_assert(HasEqualityOperator<T>::value, "T must have operator==");
320 static_assert(HasPosition<T>::value, "T must have a position member");
321 static_assert(HasPositionXY<T>::value, "T.position must have x and y members");
322 static_assert(IsCopyConstructor<T>::value, "T must be copyable");
323 static_assert(HasEuclideanDistance<T>::value, "T.position must have a euclidean_distance method");
324 static_assert(PositionXYAreDouble<T>::value, "T.position.x and T.position.y must be double");
325
326 TripleSpline<T> result;
327 const size_t n = center.size();
328
329 // Check if we have enough points and all sequences have the same size
330 if (n < 2 || left.size() != n || right.size() != n) {
331 RCLCPP_INFO(rclcpp::get_logger("rclcpp"),
332 "Invalid input for fit_triple_spline: center has %zu points, "
333 "left has %zu points, right has %zu points. All must be >= 2 and equal.",
334 center.size(), left.size(), right.size());
335 return result;
336 }
337
338 // -------- COMPUTE PARAMETRIC DOMAIN BASED ON ARC LENGTH --------
339 // Use cumulative arc length of the center line as the parameter t
340 std::vector<double> t_values(n);
341 t_values[0] = 0.0;
342
343 for (size_t i = 1; i < n; ++i) {
344 double dist = center[i].position.euclidean_distance(center[i - 1].position);
345 t_values[i] = t_values[i - 1] + dist;
346 }
347
348 // -------- EXTRACT COORDINATE DATA FROM INPUT POINTS --------
349 std::vector<double> center_x(n);
350 std::vector<double> center_y(n);
351 std::vector<double> left_x(n);
352 std::vector<double> left_y(n);
353 std::vector<double> right_x(n);
354 std::vector<double> right_y(n);
355
356 for (size_t i = 0; i < n; ++i) {
357 center_x[i] = center[i].position.x;
358 center_y[i] = center[i].position.y;
359 left_x[i] = left[i].position.x;
360 left_y[i] = left[i].position.y;
361 right_x[i] = right[i].position.x;
362 right_y[i] = right[i].position.y;
363 }
364
365 // -------- INITIALIZE GSL INTERPOLATION --------
366 gsl_interp_accel *acc = ::gsl_interp_accel_alloc();
367
368 // Select interpolation type based on order parameter
369 const gsl_interp_type *interp_type = ::gsl_interp_linear;
370 if (order == 3) {
371 interp_type = ::gsl_interp_cspline;
372 }
373
374 // -------- ALLOCATE SPLINES FOR ALL COORDINATES --------
375 // We need 6 splines total: (center, left, right) × (x, y)
376 gsl_spline *spline_center_x = ::gsl_spline_alloc(interp_type, n);
377 gsl_spline *spline_center_y = ::gsl_spline_alloc(interp_type, n);
378 gsl_spline *spline_left_x = ::gsl_spline_alloc(interp_type, n);
379 gsl_spline *spline_left_y = ::gsl_spline_alloc(interp_type, n);
380 gsl_spline *spline_right_x = ::gsl_spline_alloc(interp_type, n);
381 gsl_spline *spline_right_y = ::gsl_spline_alloc(interp_type, n);
382
383 // -------- INITIALIZE SPLINES WITH DATA --------
384 // Each spline interpolates one coordinate as a function of parameter t
385 (void)::gsl_spline_init(spline_center_x, t_values.data(), center_x.data(), n);
386 (void)::gsl_spline_init(spline_center_y, t_values.data(), center_y.data(), n);
387 (void)::gsl_spline_init(spline_left_x, t_values.data(), left_x.data(), n);
388 (void)::gsl_spline_init(spline_left_y, t_values.data(), left_y.data(), n);
389 (void)::gsl_spline_init(spline_right_x, t_values.data(), right_x.data(), n);
390 (void)::gsl_spline_init(spline_right_y, t_values.data(), right_y.data(), n);
391
392 // -------- EVALUATE SPLINES AT HIGHER RESOLUTION --------
393 const double t_min = t_values[0];
394 const double t_max = t_values[n - 1];
395 const size_t total_points = n * precision;
396
397 result.center.reserve(total_points);
398 result.left.reserve(total_points);
399 result.right.reserve(total_points);
400
401 for (size_t i = 0; i < n-1; ++i) {
402 for (int j = 0; j < precision; ++j) {
403 // Compute parameter t for this evaluation point within segment i
404 double t_start = t_values[i];
405 double t_end = t_max;
406 if (i + 1 < n) {
407 t_end = t_values[i + 1];
408 }
409 double t = t_start + (t_end - t_start) * (static_cast<double>(j) / precision);
410
411 // Clamp t to valid range
412 if (t > t_max) {
413 t = t_max;
414 }
415 if (t < t_min) {
416 t = t_min;
417 }
418
419 // -------- EVALUATE ALL SPLINES AT PARAMETER t --------
420 T point_center;
421 T point_left;
422 T point_right;
423
424 point_center.position.x = ::gsl_spline_eval(spline_center_x, t, acc);
425 point_center.position.y = ::gsl_spline_eval(spline_center_y, t, acc);
426
427 point_left.position.x = ::gsl_spline_eval(spline_left_x, t, acc);
428 point_left.position.y = ::gsl_spline_eval(spline_left_y, t, acc);
429
430 point_right.position.x = ::gsl_spline_eval(spline_right_x, t, acc);
431 point_right.position.y = ::gsl_spline_eval(spline_right_y, t, acc);
432
433 result.center.push_back(point_center);
434 result.left.push_back(point_left);
435 result.right.push_back(point_right);
436 }
437 }
438
439 // -------- CLEANUP GSL RESOURCES --------
440 ::gsl_spline_free(spline_center_x);
441 ::gsl_spline_free(spline_center_y);
442 ::gsl_spline_free(spline_left_x);
443 ::gsl_spline_free(spline_left_y);
444 ::gsl_spline_free(spline_right_x);
445 ::gsl_spline_free(spline_right_y);
446 ::gsl_interp_accel_free(acc);
447
448 RCLCPP_DEBUG(rclcpp::get_logger("rclcpp"),
449 "END fit_triple_spline with %zu center, %zu left, %zu right points",
450 result.center.size(), result.left.size(), result.right.size());
451
452 return result;
453}
454
455#endif // SRC_PLANNING_INCLUDE_UTILS_SPLINES_HPP_
Hash function for cones.
Definition cone.hpp:36
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
In this file, a function is implemented to fit a spline to a set of ordered points.
Definition splines.hpp:39
std::vector< T > right
Definition splines.hpp:99
std::vector< T > center
Definition splines.hpp:97
std::vector< T > left
Definition splines.hpp:98