blob: 665ab3ce422ccd319d490854ad91b78b7e1062f9 [file] [log] [blame]
Austin Schuh35d19872018-11-30 15:50:47 +11001#!/usr/bin/python
2
3from __future__ import print_function
4
Austin Schuh35d19872018-11-30 15:50:47 +11005from matplotlib import pylab
Austin Schuh35d19872018-11-30 15:50:47 +11006import gflags
Austin Schuh387a6872018-12-01 19:14:33 +11007import glog
8import numpy
9import scipy
10import scipy.integrate
11import sys
Austin Schuh35d19872018-11-30 15:50:47 +110012
Austin Schuh387a6872018-12-01 19:14:33 +110013"""This file is my playground for implementing spline following.
14
15All splines here are cubic bezier splines. See
16 https://en.wikipedia.org/wiki/B%C3%A9zier_curve for more details.
17"""
Austin Schuh35d19872018-11-30 15:50:47 +110018
19FLAGS = gflags.FLAGS
20
21
22def spline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110023 """Computes a Cubic Bezier curve.
Austin Schuh35d19872018-11-30 15:50:47 +110024
25 Args:
26 alpha: scalar or list of spline parameters to calculate the curve at.
27 control_points: n x 4 matrix of control points. n[:, 0] is the
28 starting point, and n[:, 3] is the ending point.
29
30 Returns:
31 n x m matrix of spline points. n is the dimension of the control
32 points, and m is the number of points in 'alpha'.
33 """
34 if numpy.isscalar(alpha):
35 alpha = [alpha]
36 alpha_matrix = [[(1.0 - a)**3.0, 3.0 * (1.0 - a)**2.0 * a,
37 3.0 * (1.0 - a) * a**2.0, a**3.0] for a in alpha]
38
39 return control_points * numpy.matrix(alpha_matrix).T
40
41
42def dspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110043 """Computes the derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +110044
45 Args:
46 alpha: scalar or list of spline parameters to calculate the curve at.
47 control_points: n x 4 matrix of control points. n[:, 0] is the
48 starting point, and n[:, 3] is the ending point.
49
50 Returns:
51 n x m matrix of spline point derivatives. n is the dimension of the
52 control points, and m is the number of points in 'alpha'.
53 """
54 if numpy.isscalar(alpha):
55 alpha = [alpha]
56 dalpha_matrix = [[
57 -3.0 * (1.0 - a)**2.0, 3.0 * (1.0 - a)**2.0 + -2.0 * 3.0 *
58 (1.0 - a) * a, -3.0 * a**2.0 + 2.0 * 3.0 * (1.0 - a) * a, 3.0 * a**2.0
59 ] for a in alpha]
60
61 return control_points * numpy.matrix(dalpha_matrix).T
62
63
64def ddspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110065 """Computes the second derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +110066
67 Args:
68 alpha: scalar or list of spline parameters to calculate the curve at.
69 control_points: n x 4 matrix of control points. n[:, 0] is the
70 starting point, and n[:, 3] is the ending point.
71
72 Returns:
73 n x m matrix of spline point second derivatives. n is the dimension of
74 the control points, and m is the number of points in 'alpha'.
75 """
76 if numpy.isscalar(alpha):
77 alpha = [alpha]
78 ddalpha_matrix = [[
79 2.0 * 3.0 * (1.0 - a),
80 -2.0 * 3.0 * (1.0 - a) + -2.0 * 3.0 * (1.0 - a) + 2.0 * 3.0 * a,
81 -2.0 * 3.0 * a + 2.0 * 3.0 * (1.0 - a) - 2.0 * 3.0 * a,
82 2.0 * 3.0 * a
83 ] for a in alpha]
84
85 return control_points * numpy.matrix(ddalpha_matrix).T
86
87
88def dddspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110089 """Computes the third derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +110090
91 Args:
92 alpha: scalar or list of spline parameters to calculate the curve at.
93 control_points: n x 4 matrix of control points. n[:, 0] is the
94 starting point, and n[:, 3] is the ending point.
95
96 Returns:
97 n x m matrix of spline point second derivatives. n is the dimension of
98 the control points, and m is the number of points in 'alpha'.
99 """
100 if numpy.isscalar(alpha):
101 alpha = [alpha]
102 ddalpha_matrix = [[
103 -2.0 * 3.0,
104 2.0 * 3.0 + 2.0 * 3.0 + 2.0 * 3.0,
105 -2.0 * 3.0 - 2.0 * 3.0 - 2.0 * 3.0,
106 2.0 * 3.0
107 ] for a in alpha]
108
109 return control_points * numpy.matrix(ddalpha_matrix).T
110
111
112def spline_theta(alpha, control_points, dspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100113 """Computes the heading of a robot following a Cubic Bezier curve at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100114
115 Args:
116 alpha: scalar or list of spline parameters to calculate the heading at.
117 control_points: n x 4 matrix of control points. n[:, 0] is the
118 starting point, and n[:, 3] is the ending point.
119
120 Returns:
121 m array of spline point headings. m is the number of points in 'alpha'.
122 """
123 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100124 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100125
126 return numpy.arctan2(
127 numpy.array(dspline_points)[1, :], numpy.array(dspline_points)[0, :])
128
129
Austin Schuh387a6872018-12-01 19:14:33 +1100130def dspline_theta(alpha,
Austin Schuh35d19872018-11-30 15:50:47 +1100131 control_points,
132 dspline_points=None,
133 ddspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100134 """Computes the derivative of the heading at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100135
Austin Schuh387a6872018-12-01 19:14:33 +1100136 This is the derivative of spline_theta wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100137
138 Args:
139 alpha: scalar or list of spline parameters to calculate the derivative
140 of the heading at.
141 control_points: n x 4 matrix of control points. n[:, 0] is the
142 starting point, and n[:, 3] is the ending point.
143
144 Returns:
145 m array of spline point heading derivatives. m is the number of points
146 in 'alpha'.
147 """
148 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100149 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100150
151 if ddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100152 ddspline_points = ddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100153
154 dx = numpy.array(dspline_points)[0, :]
155 dy = numpy.array(dspline_points)[1, :]
156
157 ddx = numpy.array(ddspline_points)[0, :]
158 ddy = numpy.array(ddspline_points)[1, :]
159
160 return 1.0 / (dx**2.0 + dy**2.0) * (dx * ddy - dy * ddx)
161
162
Austin Schuh387a6872018-12-01 19:14:33 +1100163def ddspline_theta(alpha,
Austin Schuh35d19872018-11-30 15:50:47 +1100164 control_points,
165 dspline_points=None,
166 ddspline_points=None,
167 dddspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100168 """Computes the second derivative of the heading at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100169
Austin Schuh387a6872018-12-01 19:14:33 +1100170 This is the second derivative of spline_theta wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100171
172 Args:
173 alpha: scalar or list of spline parameters to calculate the second
174 derivative of the heading at.
175 control_points: n x 4 matrix of control points. n[:, 0] is the
176 starting point, and n[:, 3] is the ending point.
177
178 Returns:
179 m array of spline point heading second derivatives. m is the number of
180 points in 'alpha'.
181 """
182 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100183 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100184
185 if ddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100186 ddspline_points = ddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100187
188 if dddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100189 dddspline_points = dddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100190
Austin Schuh387a6872018-12-01 19:14:33 +1100191 dddspline_points = dddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100192
193 dx = numpy.array(dspline_points)[0, :]
194 dy = numpy.array(dspline_points)[1, :]
195
196 ddx = numpy.array(ddspline_points)[0, :]
197 ddy = numpy.array(ddspline_points)[1, :]
198
199 dddx = numpy.array(dddspline_points)[0, :]
200 dddy = numpy.array(dddspline_points)[1, :]
201
202 return -1.0 / ((dx**2.0 + dy**2.0)**2.0) * (dx * ddy - dy * ddx) * 2.0 * (
203 dy * ddy + dx * ddx) + 1.0 / (dx**2.0 + dy**2.0) * (dx * dddy - dy *
204 dddx)
205
206
Austin Schuh387a6872018-12-01 19:14:33 +1100207class Path(object):
208 """Represents a path to follow."""
209 def __init__(self, control_points):
210 """Constructs a path given the control points."""
211 self._control_points = control_points
212
213 def spline_velocity(alpha):
214 return numpy.linalg.norm(dspline(alpha, self._control_points), axis=0)
215
216 self._point_distances = [0.0]
217 num_alpha = 100
218 # Integrate the xy velocity as a function of alpha for each step in the
219 # table to get an alpha -> distance calculation. Gaussian Quadrature
220 # is quite accurate, so we can get away with fewer points here than we
221 # might think.
222 for alpha in numpy.linspace(0.0, 1.0, num_alpha)[:-1]:
223 self._point_distances.append(
224 scipy.integrate.fixed_quad(spline_velocity, alpha, alpha + 1.0
225 / (num_alpha - 1.0))[0] +
226 self._point_distances[-1])
227
228 def distance_to_alpha(self, distance):
229 """Converts distances along the spline to alphas.
230
231 Args:
232 distance: A scalar or array of distances to convert
233
234 Returns:
235 An array of distances, (1 big if the input was a scalar)
236 """
237 if numpy.isscalar(distance):
238 return numpy.array([self._distance_to_alpha_scalar(distance)])
239 else:
240 return numpy.array([self._distance_to_alpha_scalar(d) for d in distance])
241
242 def _distance_to_alpha_scalar(self, distance):
243 """Helper to compute alpha for a distance for a single scalar."""
244 if distance <= 0.0:
245 return 0.0
246 elif distance >= self.length():
247 return 1.0
248 after_index = numpy.searchsorted(
249 self._point_distances, distance, side='right')
250 before_index = after_index - 1
251
252 # Linearly interpolate alpha from our (sorted) distance table.
253 return (distance - self._point_distances[before_index]) / (
254 self._point_distances[after_index] -
255 self._point_distances[before_index]) * (1.0 / (
256 len(self._point_distances) - 1.0)) + float(before_index) / (
257 len(self._point_distances) - 1.0)
258
259 def length(self):
260 """Returns the length of the spline (in meters)"""
261 return self._point_distances[-1]
262
263 # TODO(austin): need a better name...
264 def xy(self, distance):
265 """Returns the xy position as a function of distance."""
266 return spline(self.distance_to_alpha(distance), self._control_points)
267
268 # TODO(austin): need a better name...
269 def dxy(self, distance):
270 """Returns the xy velocity as a function of distance."""
271 dspline_point = dspline(
272 self.distance_to_alpha(distance), self._control_points)
273 return dspline_point / numpy.linalg.norm(dspline_point, axis=0)
274
275 # TODO(austin): need a better name...
276 def ddxy(self, distance):
277 """Returns the xy acceleration as a function of distance."""
278 alpha = self.distance_to_alpha(distance)
279 dspline_points = dspline(alpha, self._control_points)
280 ddspline_points = ddspline(alpha, self._control_points)
281
282 norm = numpy.linalg.norm(
283 dspline_points, axis=0)**2.0
284
285 return ddspline_points / norm - numpy.multiply(
286 dspline_points, (numpy.array(dspline_points)[0, :] *
287 numpy.array(ddspline_points)[0, :] +
288 numpy.array(dspline_points)[1, :] *
289 numpy.array(ddspline_points)[1, :]) / (norm**2.0))
290
291 def theta(self, distance, dspline_points=None):
292 """Returns the heading as a function of distance."""
293 return spline_theta(
294 self.distance_to_alpha(distance),
295 self._control_points,
296 dspline_points=dspline_points)
297
298 def dtheta(self, distance, dspline_points=None, ddspline_points=None):
299 """Returns the angular velocity as a function of distance."""
300 alpha = self.distance_to_alpha(distance)
301 if dspline_points is None:
302 dspline_points = dspline(alpha, self._control_points)
303 if ddspline_points is None:
304 ddspline_points = ddspline(alpha, self._control_points)
305
306 dtheta_points = dspline_theta(alpha, self._control_points,
307 dspline_points, ddspline_points)
308
309 return dtheta_points / numpy.linalg.norm(dspline_points, axis=0)
310
311 def ddtheta(self,
312 distance,
313 dspline_points=None,
314 ddspline_points=None,
315 dddspline_points=None):
316 """Returns the angular acceleration as a function of distance."""
317 alpha = self.distance_to_alpha(distance)
318 if dspline_points is None:
319 dspline_points = dspline(alpha, self._control_points)
320 if ddspline_points is None:
321 ddspline_points = ddspline(alpha, self._control_points)
322 if dddspline_points is None:
323 dddspline_points = dddspline(alpha, self._control_points)
324
325 dtheta_points = dspline_theta(alpha, self._control_points,
326 dspline_points, ddspline_points)
327 ddtheta_points = ddspline_theta(alpha, self._control_points,
328 dspline_points, ddspline_points,
329 dddspline_points)
330
331 # TODO(austin): Factor out the d^alpha/dd^2.
332 return ddtheta_points / numpy.linalg.norm(
333 dspline_points, axis=0)**2.0 - numpy.multiply(
334 dtheta_points, (numpy.array(dspline_points)[0, :] *
335 numpy.array(ddspline_points)[0, :] +
336 numpy.array(dspline_points)[1, :] *
337 numpy.array(ddspline_points)[1, :]) /
338 ((numpy.array(dspline_points)[0, :]**2.0 +
339 numpy.array(dspline_points)[1, :]**2.0)**2.0))
340
341
342
Austin Schuh35d19872018-11-30 15:50:47 +1100343def main(argv):
344 # Build up the control point matrix
345 start = numpy.matrix([[0.0, 0.0]]).T
346 c1 = numpy.matrix([[0.5, 0.0]]).T
347 c2 = numpy.matrix([[0.5, 1.0]]).T
348 end = numpy.matrix([[1.0, 1.0]]).T
349 control_points = numpy.hstack((start, c1, c2, end))
350
351 # The alphas to plot
352 alphas = numpy.linspace(0.0, 1.0, 1000)
353
354 # Compute x, y and the 3 derivatives
355 spline_points = spline(alphas, control_points)
356 dspline_points = dspline(alphas, control_points)
357 ddspline_points = ddspline(alphas, control_points)
358 dddspline_points = dddspline(alphas, control_points)
359
360 # Compute theta and the two derivatives
361 theta = spline_theta(alphas, control_points, dspline_points=dspline_points)
Austin Schuh387a6872018-12-01 19:14:33 +1100362 dtheta = dspline_theta(
363 alphas, control_points, dspline_points=dspline_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100364 ddtheta = ddspline_theta(
365 alphas,
366 control_points,
367 dspline_points=dspline_points,
368 dddspline_points=dddspline_points)
369
370 # Plot the control points and the spline.
371 pylab.figure()
372 pylab.plot(
373 numpy.array(control_points)[0, :],
374 numpy.array(control_points)[1, :],
375 '-o',
376 label='control')
377 pylab.plot(
378 numpy.array(spline_points)[0, :],
379 numpy.array(spline_points)[1, :],
380 label='spline')
381 pylab.legend()
382
383 # For grins, confirm that the double integral of the acceleration (with
384 # respect to the spline parameter) matches the position. This lets us
385 # confirm that the derivatives are consistent.
386 xint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
387 dxint_plot = xint_plot.copy()
388 xint = spline_points[:, 0].copy()
389 dxint = dspline_points[:, 0].copy()
390 xint_plot[:, 0] = xint
391 dxint_plot[:, 0] = dxint
392 for i in range(len(alphas) - 1):
393 xint += (alphas[i + 1] - alphas[i]) * dxint
394 dxint += (alphas[i + 1] - alphas[i]) * ddspline_points[:, i]
395 xint_plot[:, i + 1] = xint
396 dxint_plot[:, i + 1] = dxint
397
398 # Integrate up the spline velocity and heading to confirm that given a
399 # velocity (as a function of the spline parameter) and angle, we will move
400 # from the starting point to the ending point.
401 thetaint_plot = numpy.zeros((len(alphas),))
402 thetaint = theta[0]
403 dthetaint_plot = numpy.zeros((len(alphas),))
404 dthetaint = dtheta[0]
405 thetaint_plot[0] = thetaint
406 dthetaint_plot[0] = dthetaint
407
408 txint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
409 txint = spline_points[:, 0].copy()
410 txint_plot[:, 0] = txint
411 for i in range(len(alphas) - 1):
412 dalpha = alphas[i + 1] - alphas[i]
413 txint += dalpha * numpy.linalg.norm(
414 dspline_points[:, i]) * numpy.matrix(
415 [[numpy.cos(theta[i])], [numpy.sin(theta[i])]])
416 txint_plot[:, i + 1] = txint
417 thetaint += dalpha * dtheta[i]
418 dthetaint += dalpha * ddtheta[i]
419 thetaint_plot[i + 1] = thetaint
420 dthetaint_plot[i + 1] = dthetaint
421
422
423 # Now plot x, dx/dalpha, ddx/ddalpha, dddx/dddalpha, and integrals thereof
424 # to perform consistency checks.
425 pylab.figure()
426 pylab.plot(alphas, numpy.array(spline_points)[0, :], label='x')
427 pylab.plot(alphas, numpy.array(xint_plot)[0, :], label='ix')
428 pylab.plot(alphas, numpy.array(dspline_points)[0, :], label='dx')
429 pylab.plot(alphas, numpy.array(dxint_plot)[0, :], label='idx')
430 pylab.plot(alphas, numpy.array(txint_plot)[0, :], label='tix')
431 pylab.plot(alphas, numpy.array(ddspline_points)[0, :], label='ddx')
432 pylab.plot(alphas, numpy.array(dddspline_points)[0, :], label='dddx')
433 pylab.legend()
434
435 # Now do the same for y.
436 pylab.figure()
437 pylab.plot(alphas, numpy.array(spline_points)[1, :], label='y')
438 pylab.plot(alphas, numpy.array(xint_plot)[1, :], label='iy')
439 pylab.plot(alphas, numpy.array(dspline_points)[1, :], label='dy')
440 pylab.plot(alphas, numpy.array(dxint_plot)[1, :], label='idy')
441 pylab.plot(alphas, numpy.array(txint_plot)[1, :], label='tiy')
442 pylab.plot(alphas, numpy.array(ddspline_points)[1, :], label='ddy')
443 pylab.plot(alphas, numpy.array(dddspline_points)[1, :], label='dddy')
444 pylab.legend()
445
446 # And for theta.
447 pylab.figure()
448 pylab.plot(alphas, theta, label='theta')
449 pylab.plot(alphas, dtheta, label='dtheta')
450 pylab.plot(alphas, ddtheta, label='ddtheta')
451 pylab.plot(alphas, thetaint_plot, label='thetai')
452 pylab.plot(alphas, dthetaint_plot, label='dthetai')
Austin Schuh387a6872018-12-01 19:14:33 +1100453 pylab.plot(
454 alphas,
455 numpy.linalg.norm(
456 numpy.array(dspline_points), axis=0),
457 label='velocity')
458
459 # Now, repeat as a function of path length as opposed to alpha
460 path = Path(control_points)
461 distance_count = 1000
462 position = path.xy(0.0)
463 velocity = path.dxy(0.0)
464 theta = path.theta(0.0)
465 omega = path.dtheta(0.0)
466
467 iposition_plot = numpy.matrix(numpy.zeros((2, distance_count)))
468 ivelocity_plot = numpy.matrix(numpy.zeros((2, distance_count)))
469 iposition_plot[:, 0] = position.copy()
470 ivelocity_plot[:, 0] = velocity.copy()
471 itheta_plot = numpy.zeros((distance_count,))
472 iomega_plot = numpy.zeros((distance_count,))
473 itheta_plot[0] = theta
474 iomega_plot[0] = omega
475
476 distances = numpy.linspace(0.0, path.length(), distance_count)
477
478 for i in xrange(len(distances) - 1):
479 position += velocity * (distances[i + 1] - distances[i])
480 velocity += path.ddxy(distances[i]) * (distances[i + 1] - distances[i])
481 iposition_plot[:, i + 1] = position
482 ivelocity_plot[:, i + 1] = velocity
483
484 theta += omega * (distances[i + 1] - distances[i])
485 omega += path.ddtheta(distances[i]) * (distances[i + 1] - distances[i])
486 itheta_plot[i + 1] = theta
487 iomega_plot[i + 1] = omega
488
489 pylab.figure()
490 pylab.plot(distances, numpy.array(path.xy(distances))[0, :], label='x')
491 pylab.plot(distances, numpy.array(iposition_plot)[0, :], label='ix')
492 pylab.plot(distances, numpy.array(path.dxy(distances))[0, :], label='dx')
493 pylab.plot(distances, numpy.array(ivelocity_plot)[0, :], label='idx')
494 pylab.plot(distances, numpy.array(path.ddxy(distances))[0, :], label='ddx')
495 pylab.legend()
496
497 pylab.figure()
498 pylab.plot(distances, numpy.array(path.xy(distances))[1, :], label='y')
499 pylab.plot(distances, numpy.array(iposition_plot)[1, :], label='iy')
500 pylab.plot(distances, numpy.array(path.dxy(distances))[1, :], label='dy')
501 pylab.plot(distances, numpy.array(ivelocity_plot)[1, :], label='idy')
502 pylab.plot(distances, numpy.array(path.ddxy(distances))[1, :], label='ddy')
503 pylab.legend()
504
505 pylab.figure()
506 pylab.plot(distances, path.theta(distances), label='theta')
507 pylab.plot(distances, itheta_plot, label='itheta')
508 pylab.plot(distances, path.dtheta(distances), label='omega')
509 pylab.plot(distances, iomega_plot, label='iomega')
510 pylab.plot(distances, path.ddtheta(distances), label='alpha')
511 pylab.legend()
Austin Schuh35d19872018-11-30 15:50:47 +1100512
513 # TODO(austin): Start creating a velocity plan now that we have all the
514 # derivitives of our spline.
515
Austin Schuh35d19872018-11-30 15:50:47 +1100516 pylab.show()
517
518
519if __name__ == '__main__':
520 argv = FLAGS(sys.argv)
521 sys.exit(main(argv))