blob: 585d6b87e548f7ecff9f26f3bc6d31d6d5023eba [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 Schuh44aa9142018-12-03 21:07:23 +110013from frc971.control_loops.python import polydrivetrain
Austin Schuh8cb98eb2018-12-05 22:06:45 +110014from frc971.control_loops.python import drivetrain
Austin Schuh44aa9142018-12-03 21:07:23 +110015import y2018.control_loops.python.drivetrain
16
Austin Schuh387a6872018-12-01 19:14:33 +110017"""This file is my playground for implementing spline following.
18
19All splines here are cubic bezier splines. See
20 https://en.wikipedia.org/wiki/B%C3%A9zier_curve for more details.
21"""
Austin Schuh35d19872018-11-30 15:50:47 +110022
23FLAGS = gflags.FLAGS
24
25
Austin Schuh8cb98eb2018-12-05 22:06:45 +110026def RungeKutta(f, y0, t, h, count=1):
27 """4th order RungeKutta integration of dy/dt = f(t, y) starting at X."""
28 y1 = y0
29 dh = h / float(count)
30 for x in xrange(count):
31 k1 = dh * f(t + dh * x, y1)
32 k2 = dh * f(t + dh * x + dh / 2.0, y1 + k1 / 2.0)
33 k3 = dh * f(t + dh * x + dh / 2.0, y1 + k2 / 2.0)
34 k4 = dh * f(t + dh * x + dh, y1 + k3)
35 y1 += (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0
36 return y1
37
38
Austin Schuh35d19872018-11-30 15:50:47 +110039def spline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110040 """Computes a Cubic Bezier curve.
Austin Schuh35d19872018-11-30 15:50:47 +110041
42 Args:
43 alpha: scalar or list of spline parameters to calculate the curve at.
44 control_points: n x 4 matrix of control points. n[:, 0] is the
45 starting point, and n[:, 3] is the ending point.
46
47 Returns:
48 n x m matrix of spline points. n is the dimension of the control
49 points, and m is the number of points in 'alpha'.
50 """
51 if numpy.isscalar(alpha):
52 alpha = [alpha]
53 alpha_matrix = [[(1.0 - a)**3.0, 3.0 * (1.0 - a)**2.0 * a,
54 3.0 * (1.0 - a) * a**2.0, a**3.0] for a in alpha]
55
56 return control_points * numpy.matrix(alpha_matrix).T
57
58
59def dspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110060 """Computes the derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +110061
62 Args:
63 alpha: scalar or list of spline parameters to calculate the curve at.
64 control_points: n x 4 matrix of control points. n[:, 0] is the
65 starting point, and n[:, 3] is the ending point.
66
67 Returns:
68 n x m matrix of spline point derivatives. n is the dimension of the
69 control points, and m is the number of points in 'alpha'.
70 """
71 if numpy.isscalar(alpha):
72 alpha = [alpha]
73 dalpha_matrix = [[
74 -3.0 * (1.0 - a)**2.0, 3.0 * (1.0 - a)**2.0 + -2.0 * 3.0 *
75 (1.0 - a) * a, -3.0 * a**2.0 + 2.0 * 3.0 * (1.0 - a) * a, 3.0 * a**2.0
76 ] for a in alpha]
77
78 return control_points * numpy.matrix(dalpha_matrix).T
79
80
81def ddspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +110082 """Computes the second derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +110083
84 Args:
85 alpha: scalar or list of spline parameters to calculate the curve at.
86 control_points: n x 4 matrix of control points. n[:, 0] is the
87 starting point, and n[:, 3] is the ending point.
88
89 Returns:
90 n x m matrix of spline point second derivatives. n is the dimension of
91 the control points, and m is the number of points in 'alpha'.
92 """
93 if numpy.isscalar(alpha):
94 alpha = [alpha]
95 ddalpha_matrix = [[
96 2.0 * 3.0 * (1.0 - a),
97 -2.0 * 3.0 * (1.0 - a) + -2.0 * 3.0 * (1.0 - a) + 2.0 * 3.0 * a,
98 -2.0 * 3.0 * a + 2.0 * 3.0 * (1.0 - a) - 2.0 * 3.0 * a,
99 2.0 * 3.0 * a
100 ] for a in alpha]
101
102 return control_points * numpy.matrix(ddalpha_matrix).T
103
104
105def dddspline(alpha, control_points):
Austin Schuh387a6872018-12-01 19:14:33 +1100106 """Computes the third derivative of a Cubic Bezier curve wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100107
108 Args:
109 alpha: scalar or list of spline parameters to calculate the curve at.
110 control_points: n x 4 matrix of control points. n[:, 0] is the
111 starting point, and n[:, 3] is the ending point.
112
113 Returns:
114 n x m matrix of spline point second derivatives. n is the dimension of
115 the control points, and m is the number of points in 'alpha'.
116 """
117 if numpy.isscalar(alpha):
118 alpha = [alpha]
119 ddalpha_matrix = [[
120 -2.0 * 3.0,
121 2.0 * 3.0 + 2.0 * 3.0 + 2.0 * 3.0,
122 -2.0 * 3.0 - 2.0 * 3.0 - 2.0 * 3.0,
123 2.0 * 3.0
124 ] for a in alpha]
125
126 return control_points * numpy.matrix(ddalpha_matrix).T
127
128
129def spline_theta(alpha, control_points, dspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100130 """Computes the heading of a robot following a Cubic Bezier curve at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100131
132 Args:
133 alpha: scalar or list of spline parameters to calculate the heading at.
134 control_points: n x 4 matrix of control points. n[:, 0] is the
135 starting point, and n[:, 3] is the ending point.
136
137 Returns:
138 m array of spline point headings. m is the number of points in 'alpha'.
139 """
140 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100141 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100142
143 return numpy.arctan2(
144 numpy.array(dspline_points)[1, :], numpy.array(dspline_points)[0, :])
145
146
Austin Schuh387a6872018-12-01 19:14:33 +1100147def dspline_theta(alpha,
Austin Schuh35d19872018-11-30 15:50:47 +1100148 control_points,
149 dspline_points=None,
150 ddspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100151 """Computes the derivative of the heading at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100152
Austin Schuh387a6872018-12-01 19:14:33 +1100153 This is the derivative of spline_theta wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100154
155 Args:
156 alpha: scalar or list of spline parameters to calculate the derivative
157 of the heading at.
158 control_points: n x 4 matrix of control points. n[:, 0] is the
159 starting point, and n[:, 3] is the ending point.
160
161 Returns:
162 m array of spline point heading derivatives. m is the number of points
163 in 'alpha'.
164 """
165 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100166 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100167
168 if ddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100169 ddspline_points = ddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100170
171 dx = numpy.array(dspline_points)[0, :]
172 dy = numpy.array(dspline_points)[1, :]
173
174 ddx = numpy.array(ddspline_points)[0, :]
175 ddy = numpy.array(ddspline_points)[1, :]
176
177 return 1.0 / (dx**2.0 + dy**2.0) * (dx * ddy - dy * ddx)
178
179
Austin Schuh387a6872018-12-01 19:14:33 +1100180def ddspline_theta(alpha,
Austin Schuh35d19872018-11-30 15:50:47 +1100181 control_points,
182 dspline_points=None,
183 ddspline_points=None,
184 dddspline_points=None):
Austin Schuh387a6872018-12-01 19:14:33 +1100185 """Computes the second derivative of the heading at alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100186
Austin Schuh387a6872018-12-01 19:14:33 +1100187 This is the second derivative of spline_theta wrt alpha.
Austin Schuh35d19872018-11-30 15:50:47 +1100188
189 Args:
190 alpha: scalar or list of spline parameters to calculate the second
191 derivative of the heading at.
192 control_points: n x 4 matrix of control points. n[:, 0] is the
193 starting point, and n[:, 3] is the ending point.
194
195 Returns:
196 m array of spline point heading second derivatives. m is the number of
197 points in 'alpha'.
198 """
199 if dspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100200 dspline_points = dspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100201
202 if ddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100203 ddspline_points = ddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100204
205 if dddspline_points is None:
Austin Schuh387a6872018-12-01 19:14:33 +1100206 dddspline_points = dddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100207
Austin Schuh387a6872018-12-01 19:14:33 +1100208 dddspline_points = dddspline(alpha, control_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100209
210 dx = numpy.array(dspline_points)[0, :]
211 dy = numpy.array(dspline_points)[1, :]
212
213 ddx = numpy.array(ddspline_points)[0, :]
214 ddy = numpy.array(ddspline_points)[1, :]
215
216 dddx = numpy.array(dddspline_points)[0, :]
217 dddy = numpy.array(dddspline_points)[1, :]
218
219 return -1.0 / ((dx**2.0 + dy**2.0)**2.0) * (dx * ddy - dy * ddx) * 2.0 * (
220 dy * ddy + dx * ddx) + 1.0 / (dx**2.0 + dy**2.0) * (dx * dddy - dy *
221 dddx)
222
223
Austin Schuh387a6872018-12-01 19:14:33 +1100224class Path(object):
225 """Represents a path to follow."""
226 def __init__(self, control_points):
227 """Constructs a path given the control points."""
228 self._control_points = control_points
229
230 def spline_velocity(alpha):
231 return numpy.linalg.norm(dspline(alpha, self._control_points), axis=0)
232
233 self._point_distances = [0.0]
234 num_alpha = 100
235 # Integrate the xy velocity as a function of alpha for each step in the
236 # table to get an alpha -> distance calculation. Gaussian Quadrature
237 # is quite accurate, so we can get away with fewer points here than we
238 # might think.
239 for alpha in numpy.linspace(0.0, 1.0, num_alpha)[:-1]:
240 self._point_distances.append(
241 scipy.integrate.fixed_quad(spline_velocity, alpha, alpha + 1.0
242 / (num_alpha - 1.0))[0] +
243 self._point_distances[-1])
244
245 def distance_to_alpha(self, distance):
246 """Converts distances along the spline to alphas.
247
248 Args:
249 distance: A scalar or array of distances to convert
250
251 Returns:
252 An array of distances, (1 big if the input was a scalar)
253 """
254 if numpy.isscalar(distance):
255 return numpy.array([self._distance_to_alpha_scalar(distance)])
256 else:
257 return numpy.array([self._distance_to_alpha_scalar(d) for d in distance])
258
259 def _distance_to_alpha_scalar(self, distance):
260 """Helper to compute alpha for a distance for a single scalar."""
261 if distance <= 0.0:
262 return 0.0
263 elif distance >= self.length():
264 return 1.0
265 after_index = numpy.searchsorted(
266 self._point_distances, distance, side='right')
267 before_index = after_index - 1
268
269 # Linearly interpolate alpha from our (sorted) distance table.
270 return (distance - self._point_distances[before_index]) / (
271 self._point_distances[after_index] -
272 self._point_distances[before_index]) * (1.0 / (
273 len(self._point_distances) - 1.0)) + float(before_index) / (
274 len(self._point_distances) - 1.0)
275
276 def length(self):
277 """Returns the length of the spline (in meters)"""
278 return self._point_distances[-1]
279
280 # TODO(austin): need a better name...
281 def xy(self, distance):
282 """Returns the xy position as a function of distance."""
283 return spline(self.distance_to_alpha(distance), self._control_points)
284
285 # TODO(austin): need a better name...
286 def dxy(self, distance):
287 """Returns the xy velocity as a function of distance."""
288 dspline_point = dspline(
289 self.distance_to_alpha(distance), self._control_points)
290 return dspline_point / numpy.linalg.norm(dspline_point, axis=0)
291
292 # TODO(austin): need a better name...
293 def ddxy(self, distance):
294 """Returns the xy acceleration as a function of distance."""
295 alpha = self.distance_to_alpha(distance)
296 dspline_points = dspline(alpha, self._control_points)
297 ddspline_points = ddspline(alpha, self._control_points)
298
299 norm = numpy.linalg.norm(
300 dspline_points, axis=0)**2.0
301
302 return ddspline_points / norm - numpy.multiply(
303 dspline_points, (numpy.array(dspline_points)[0, :] *
304 numpy.array(ddspline_points)[0, :] +
305 numpy.array(dspline_points)[1, :] *
306 numpy.array(ddspline_points)[1, :]) / (norm**2.0))
307
308 def theta(self, distance, dspline_points=None):
309 """Returns the heading as a function of distance."""
310 return spline_theta(
311 self.distance_to_alpha(distance),
312 self._control_points,
313 dspline_points=dspline_points)
314
315 def dtheta(self, distance, dspline_points=None, ddspline_points=None):
316 """Returns the angular velocity 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
323 dtheta_points = dspline_theta(alpha, self._control_points,
324 dspline_points, ddspline_points)
325
326 return dtheta_points / numpy.linalg.norm(dspline_points, axis=0)
327
328 def ddtheta(self,
329 distance,
330 dspline_points=None,
331 ddspline_points=None,
332 dddspline_points=None):
333 """Returns the angular acceleration as a function of distance."""
334 alpha = self.distance_to_alpha(distance)
335 if dspline_points is None:
336 dspline_points = dspline(alpha, self._control_points)
337 if ddspline_points is None:
338 ddspline_points = ddspline(alpha, self._control_points)
339 if dddspline_points is None:
340 dddspline_points = dddspline(alpha, self._control_points)
341
342 dtheta_points = dspline_theta(alpha, self._control_points,
343 dspline_points, ddspline_points)
344 ddtheta_points = ddspline_theta(alpha, self._control_points,
345 dspline_points, ddspline_points,
346 dddspline_points)
347
348 # TODO(austin): Factor out the d^alpha/dd^2.
349 return ddtheta_points / numpy.linalg.norm(
350 dspline_points, axis=0)**2.0 - numpy.multiply(
351 dtheta_points, (numpy.array(dspline_points)[0, :] *
352 numpy.array(ddspline_points)[0, :] +
353 numpy.array(dspline_points)[1, :] *
354 numpy.array(ddspline_points)[1, :]) /
355 ((numpy.array(dspline_points)[0, :]**2.0 +
356 numpy.array(dspline_points)[1, :]**2.0)**2.0))
357
358
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100359def integrate_accel_for_distance(f, v, x, dx):
360 # Use a trick from
361 # https://www.johndcook.com/blog/2012/02/21/care-and-treatment-of-singularities/
362 #
363 # We want to calculate:
364 # v0 + (integral of dv/dt = f(x, v) from x to x + dx); noting that dv/dt
365 # is expressed in t, not distance, so we want to do the integral of
366 # dv/dx = f(x, v) / v.
367 #
368 # Because v can be near zero at the start of the integral (but because f is
369 # nonnegative, v will never go to zero), but the integral should still be
370 # valid, we follow the suggestion and instead calculate
371 # v0 + integral((f(x, v) - f(x0, v0)) / v) + integral(f(x0, v0) / v).
372 #
373 # Using a0 = f(x0, v0), we get the second term as
374 # integral((f(x, v) - a0) / v)
375 # where when v is zero we will also be at x0/v0 (because v can only start
376 # at zero, not go to zero).
377 #
378 # The second term, integral(a0 / v) requires an approximation.--in
379 # this case, that dv/dt is constant. Thus, we have
380 # integral(a0 / sqrt(v0^2 + 2*a0*x)) = sqrt(2*a0*dx + v0^2) - sqrt(v0^2)
381 # = sqrt(2 * a0 * dx * v0^2) - v0.
382 #
383 # Because the RungeKutta function returns v0 + the integral, this
384 # gives the statements below.
385
386 a0 = f(x, v)
387
388 def integrablef(t, y):
389 # Since we know that a0 == a(0) and that they are asymptotically the
390 # same at 0, we know that the limit is 0 at 0. This is true because
391 # when starting from a stop, under sane accelerations, we can assume
392 # that we will start with a constant acceleration. So, hard-code it.
393 if numpy.abs(y) < 1e-6:
394 return 0.0
395 return (f(t, y) - a0) / y
396
397 return (RungeKutta(integrablef, v, x, dx) - v) + numpy.sqrt(2.0 * a0 * dx + v * v)
398
399
400class Trajectory(object):
401 def __init__(self, path, drivetrain, longitudal_accel, lateral_accel, distance_count):
402 self._path = path
403 self._drivetrain = drivetrain
404 self.distances = numpy.linspace(0.0,
405 self._path.length(), distance_count)
406 self._longitudal_accel = longitudal_accel
407 self._lateral_accel = lateral_accel
408
409 self._B_inverse = numpy.linalg.inv(self._drivetrain.B_continuous)
410
411 def create_plan(self, vmax):
412 vmax = 10.0
413 plan = numpy.array(numpy.zeros((len(self.distances), )))
414 plan.fill(vmax)
415 return plan
416
417 def lateral_velocity_curvature(self, distance):
418 return numpy.sqrt(self._lateral_accel /
419 numpy.linalg.norm(self._path.ddxy(distance)))
420
421 def lateral_accel_pass(self, plan):
422 plan = plan.copy()
423 # TODO(austin): This appears to be doing nothing.
424 for i, distance in enumerate(self.distances):
425 plan[i] = min(plan[i], self.lateral_velocity_curvature(distance))
426 return plan
427
428 def compute_K345(self, current_dtheta, current_ddtheta):
429 # We've now got the equation:
430 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
431 K1 = numpy.matrix(
432 [[-self._drivetrain.robot_radius_l * current_ddtheta],
433 [self._drivetrain.robot_radius_r * current_ddtheta]])
434 K2 = numpy.matrix(
435 [[1.0 - self._drivetrain.robot_radius_l * current_dtheta],
436 [1.0 + self._drivetrain.robot_radius_r * current_dtheta]])
437
438 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
439 K3 = self._B_inverse * K1
440 K4 = -self._B_inverse * self._drivetrain.A_continuous * K2
441 K5 = self._B_inverse * K2
442 return K3, K4, K5
443
444 def curvature_voltage_pass(self, plan):
445 plan = plan.copy()
446 for i, distance in enumerate(self.distances):
447 current_ddtheta = self._path.ddtheta(distance)[0]
448 current_dtheta = self._path.dtheta(distance)[0]
449 # We've now got the equation:
450 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
451 # Now, rephrase it as K3 v^2 + K4 v = U
452 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
453 # But, we are going to assume that d^2x/dt^2 = 0
454
455 x = []
456 for a, b in [(K3[0, 0], K4[0, 0]), (K3[1, 0], K4[1, 0])]:
457 for c in [12.0, -12.0]:
458 middle = b * b - 4.0 * a * c
459 if middle >= 0.0:
460 x.append((-b + numpy.sqrt(middle)) / (2.0 * a))
461 x.append((-b - numpy.sqrt(middle)) / (2.0 * a))
462
463 maxx = 0.0
464 for newx in x:
465 if newx < 0.0:
466 continue
467 U = (K3 * newx * newx + K4 * newx).T
468 # TODO(austin): We know that one of these *will* be +-12.0. Only
469 # check the other one.
470 if not (numpy.abs(U) > 12.0 + 1e-6).any():
471 maxx = max(newx, maxx)
472
473 if maxx == 0.0:
474 print('Could not solve')
475 return None
476 plan[i] = min(plan[i], maxx)
477 pass
478 return plan
479
480 def forward_acceleration(self, x, v):
481 current_ddtheta = self._path.ddtheta(x)[0]
482 current_dtheta = self._path.dtheta(x)[0]
483 # We've now got the equation:
484 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
485 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
486 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
487
488 C = K3 * v * v + K4 * v
489 # Note: K345 are not quite constant over the step, but we are going
490 # to assume they are for now.
491 accelerations = [(12.0 - C[0, 0]) / K5[0, 0], (12.0 - C[1, 0]) /
492 K5[1, 0], (-12.0 - C[0, 0]) / K5[0, 0],
493 (-12.0 - C[1, 0]) / K5[1, 0]]
494 maxa = 0.0
495 for a in accelerations:
496 if a < 0.0:
497 continue
498
499 U = K5 * a + K3 * v * v + K4 * v
500 if not (numpy.abs(U) > 12.0 + 1e-6).any():
501 maxa = max(maxa, a)
502
503 lateral_accel = v * v * numpy.linalg.norm(self._path.ddxy(x))
504 # Constrain the longitudinal acceleration to keep in a pseudo friction
505 # circle. This will make it so we don't floor it while in a turn and
506 # cause extra wheel slip.
507 long_accel = numpy.sqrt(1.0 - (lateral_accel / self._lateral_accel)**
508 2.0) * self._longitudal_accel
509 return min(long_accel, maxa)
510
511 def forward_pass(self, plan):
512 plan = plan.copy()
513 for i, distance in enumerate(self.distances):
514 if i == len(self.distances) - 1:
515 break
516
517 plan[i + 1] = min(
518 plan[i + 1],
519 integrate_accel_for_distance(
520 self.forward_acceleration, plan[i], self.distances[i],
521 self.distances[i + 1] - self.distances[i]))
522 return plan
523
524 def backward_acceleration(self, x, v):
525 # TODO(austin): Forwards and backwards are quite similar. Can we
526 # factor this out?
527 current_ddtheta = self._path.ddtheta(x)[0]
528 current_dtheta = self._path.dtheta(x)[0]
529 # We've now got the equation:
530 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
531 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
532 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
533
534 C = K3 * v * v + K4 * v
535 # Note: K345 are not quite constant over the step, but we are going
536 # to assume they are for now.
537 accelerations = [(12.0 - C[0, 0]) / K5[0, 0], (12.0 - C[1, 0]) /
538 K5[1, 0], (-12.0 - C[0, 0]) / K5[0, 0],
539 (-12.0 - C[1, 0]) / K5[1, 0]]
540 mina = 0.0
541 for a in accelerations:
542 if a > 0.0:
543 continue
544
545 U = K5 * a + K3 * v * v + K4 * v
546 if not (numpy.abs(U) > 12.0 + 1e-6).any():
547 mina = min(mina, a)
548
549 lateral_accel = v * v * numpy.linalg.norm(self._path.ddxy(x))
550 # Constrain the longitudinal acceleration to keep in a pseudo friction
551 # circle. This will make it so we don't floor it while in a turn and
552 # cause extra wheel slip.
553 long_accel = -numpy.sqrt(1.0 - (lateral_accel / self._lateral_accel)**
554 2.0) * self._longitudal_accel
555 return max(long_accel, mina)
556
557 def backward_pass(self, plan):
558 plan = plan.copy()
559 for i, distance in reversed(list(enumerate(self.distances))):
560 if i == 0:
561 break
562
563 plan[i - 1] = min(
564 plan[i - 1],
565 integrate_accel_for_distance(
566 self.backward_acceleration, plan[i], self.distances[i],
567 self.distances[i - 1] - self.distances[i]))
568 return plan
569
570 # TODO(austin): The plan should probably not be passed in...
571 def ff_voltage(self, plan, distance):
572 if distance < self.distances[1]:
573 after_index = 1
574 before_index = after_index - 1
575 if distance < self.distances[0]:
576 distance = 0.0
577 elif distance > self.distances[-2]:
578 after_index = len(self.distances) - 1
579 before_index = after_index - 1
580 if distance > self.distances[-1]:
581 distance = self.distances[-1]
582 else:
583 after_index = numpy.searchsorted(
584 self.distances, distance, side='right')
585 before_index = after_index - 1
586
587 vforwards = integrate_accel_for_distance(
588 self.forward_acceleration, plan[before_index],
589 self.distances[before_index],
590 distance - self.distances[before_index])
591 vbackward = integrate_accel_for_distance(
592 self.backward_acceleration, plan[after_index],
593 self.distances[after_index],
594 distance - self.distances[after_index])
595
596 vcurvature = self.lateral_velocity_curvature(distance)
597
598 if vcurvature < vforwards and vcurvature < vbackward:
599 accel = 0
600 velocity = vcurvature
601 elif vforwards < vbackward:
602 velocity = vforwards
603 accel = self.forward_acceleration(distance, velocity)
604 else:
605 velocity = vbackward
606 accel = self.backward_acceleration(distance, velocity)
607
608 current_ddtheta = self._path.ddtheta(distance)[0]
609 current_dtheta = self._path.dtheta(distance)[0]
610 # TODO(austin): Factor these out.
611 # We've now got the equation:
612 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
613 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
614 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
615
616 U = K5 * accel + K3 * velocity * velocity + K4 * velocity
617 return U
Austin Schuh387a6872018-12-01 19:14:33 +1100618
Austin Schuh35d19872018-11-30 15:50:47 +1100619def main(argv):
620 # Build up the control point matrix
621 start = numpy.matrix([[0.0, 0.0]]).T
622 c1 = numpy.matrix([[0.5, 0.0]]).T
623 c2 = numpy.matrix([[0.5, 1.0]]).T
624 end = numpy.matrix([[1.0, 1.0]]).T
625 control_points = numpy.hstack((start, c1, c2, end))
626
627 # The alphas to plot
628 alphas = numpy.linspace(0.0, 1.0, 1000)
629
630 # Compute x, y and the 3 derivatives
631 spline_points = spline(alphas, control_points)
632 dspline_points = dspline(alphas, control_points)
633 ddspline_points = ddspline(alphas, control_points)
634 dddspline_points = dddspline(alphas, control_points)
635
636 # Compute theta and the two derivatives
637 theta = spline_theta(alphas, control_points, dspline_points=dspline_points)
Austin Schuh387a6872018-12-01 19:14:33 +1100638 dtheta = dspline_theta(
639 alphas, control_points, dspline_points=dspline_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100640 ddtheta = ddspline_theta(
641 alphas,
642 control_points,
643 dspline_points=dspline_points,
644 dddspline_points=dddspline_points)
645
646 # Plot the control points and the spline.
647 pylab.figure()
648 pylab.plot(
649 numpy.array(control_points)[0, :],
650 numpy.array(control_points)[1, :],
651 '-o',
652 label='control')
653 pylab.plot(
654 numpy.array(spline_points)[0, :],
655 numpy.array(spline_points)[1, :],
656 label='spline')
657 pylab.legend()
658
659 # For grins, confirm that the double integral of the acceleration (with
660 # respect to the spline parameter) matches the position. This lets us
661 # confirm that the derivatives are consistent.
662 xint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
663 dxint_plot = xint_plot.copy()
664 xint = spline_points[:, 0].copy()
665 dxint = dspline_points[:, 0].copy()
666 xint_plot[:, 0] = xint
667 dxint_plot[:, 0] = dxint
668 for i in range(len(alphas) - 1):
669 xint += (alphas[i + 1] - alphas[i]) * dxint
670 dxint += (alphas[i + 1] - alphas[i]) * ddspline_points[:, i]
671 xint_plot[:, i + 1] = xint
672 dxint_plot[:, i + 1] = dxint
673
674 # Integrate up the spline velocity and heading to confirm that given a
675 # velocity (as a function of the spline parameter) and angle, we will move
676 # from the starting point to the ending point.
677 thetaint_plot = numpy.zeros((len(alphas),))
678 thetaint = theta[0]
679 dthetaint_plot = numpy.zeros((len(alphas),))
680 dthetaint = dtheta[0]
681 thetaint_plot[0] = thetaint
682 dthetaint_plot[0] = dthetaint
683
684 txint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
685 txint = spline_points[:, 0].copy()
686 txint_plot[:, 0] = txint
687 for i in range(len(alphas) - 1):
688 dalpha = alphas[i + 1] - alphas[i]
689 txint += dalpha * numpy.linalg.norm(
690 dspline_points[:, i]) * numpy.matrix(
691 [[numpy.cos(theta[i])], [numpy.sin(theta[i])]])
692 txint_plot[:, i + 1] = txint
693 thetaint += dalpha * dtheta[i]
694 dthetaint += dalpha * ddtheta[i]
695 thetaint_plot[i + 1] = thetaint
696 dthetaint_plot[i + 1] = dthetaint
697
698
699 # Now plot x, dx/dalpha, ddx/ddalpha, dddx/dddalpha, and integrals thereof
700 # to perform consistency checks.
701 pylab.figure()
702 pylab.plot(alphas, numpy.array(spline_points)[0, :], label='x')
703 pylab.plot(alphas, numpy.array(xint_plot)[0, :], label='ix')
704 pylab.plot(alphas, numpy.array(dspline_points)[0, :], label='dx')
705 pylab.plot(alphas, numpy.array(dxint_plot)[0, :], label='idx')
706 pylab.plot(alphas, numpy.array(txint_plot)[0, :], label='tix')
707 pylab.plot(alphas, numpy.array(ddspline_points)[0, :], label='ddx')
708 pylab.plot(alphas, numpy.array(dddspline_points)[0, :], label='dddx')
709 pylab.legend()
710
711 # Now do the same for y.
712 pylab.figure()
713 pylab.plot(alphas, numpy.array(spline_points)[1, :], label='y')
714 pylab.plot(alphas, numpy.array(xint_plot)[1, :], label='iy')
715 pylab.plot(alphas, numpy.array(dspline_points)[1, :], label='dy')
716 pylab.plot(alphas, numpy.array(dxint_plot)[1, :], label='idy')
717 pylab.plot(alphas, numpy.array(txint_plot)[1, :], label='tiy')
718 pylab.plot(alphas, numpy.array(ddspline_points)[1, :], label='ddy')
719 pylab.plot(alphas, numpy.array(dddspline_points)[1, :], label='dddy')
720 pylab.legend()
721
722 # And for theta.
723 pylab.figure()
724 pylab.plot(alphas, theta, label='theta')
725 pylab.plot(alphas, dtheta, label='dtheta')
726 pylab.plot(alphas, ddtheta, label='ddtheta')
727 pylab.plot(alphas, thetaint_plot, label='thetai')
728 pylab.plot(alphas, dthetaint_plot, label='dthetai')
Austin Schuh387a6872018-12-01 19:14:33 +1100729 pylab.plot(
730 alphas,
731 numpy.linalg.norm(
732 numpy.array(dspline_points), axis=0),
733 label='velocity')
734
735 # Now, repeat as a function of path length as opposed to alpha
736 path = Path(control_points)
737 distance_count = 1000
738 position = path.xy(0.0)
739 velocity = path.dxy(0.0)
740 theta = path.theta(0.0)
741 omega = path.dtheta(0.0)
742
743 iposition_plot = numpy.matrix(numpy.zeros((2, distance_count)))
744 ivelocity_plot = numpy.matrix(numpy.zeros((2, distance_count)))
745 iposition_plot[:, 0] = position.copy()
746 ivelocity_plot[:, 0] = velocity.copy()
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100747 itheta_plot = numpy.zeros((distance_count, ))
748 iomega_plot = numpy.zeros((distance_count, ))
Austin Schuh387a6872018-12-01 19:14:33 +1100749 itheta_plot[0] = theta
750 iomega_plot[0] = omega
751
752 distances = numpy.linspace(0.0, path.length(), distance_count)
753
754 for i in xrange(len(distances) - 1):
755 position += velocity * (distances[i + 1] - distances[i])
756 velocity += path.ddxy(distances[i]) * (distances[i + 1] - distances[i])
757 iposition_plot[:, i + 1] = position
758 ivelocity_plot[:, i + 1] = velocity
759
760 theta += omega * (distances[i + 1] - distances[i])
761 omega += path.ddtheta(distances[i]) * (distances[i + 1] - distances[i])
762 itheta_plot[i + 1] = theta
763 iomega_plot[i + 1] = omega
764
765 pylab.figure()
766 pylab.plot(distances, numpy.array(path.xy(distances))[0, :], label='x')
767 pylab.plot(distances, numpy.array(iposition_plot)[0, :], label='ix')
768 pylab.plot(distances, numpy.array(path.dxy(distances))[0, :], label='dx')
769 pylab.plot(distances, numpy.array(ivelocity_plot)[0, :], label='idx')
770 pylab.plot(distances, numpy.array(path.ddxy(distances))[0, :], label='ddx')
771 pylab.legend()
772
773 pylab.figure()
774 pylab.plot(distances, numpy.array(path.xy(distances))[1, :], label='y')
775 pylab.plot(distances, numpy.array(iposition_plot)[1, :], label='iy')
776 pylab.plot(distances, numpy.array(path.dxy(distances))[1, :], label='dy')
777 pylab.plot(distances, numpy.array(ivelocity_plot)[1, :], label='idy')
778 pylab.plot(distances, numpy.array(path.ddxy(distances))[1, :], label='ddy')
779 pylab.legend()
780
781 pylab.figure()
782 pylab.plot(distances, path.theta(distances), label='theta')
783 pylab.plot(distances, itheta_plot, label='itheta')
784 pylab.plot(distances, path.dtheta(distances), label='omega')
785 pylab.plot(distances, iomega_plot, label='iomega')
786 pylab.plot(distances, path.ddtheta(distances), label='alpha')
787 pylab.legend()
Austin Schuh35d19872018-11-30 15:50:47 +1100788
789 # TODO(austin): Start creating a velocity plan now that we have all the
790 # derivitives of our spline.
791
Austin Schuh44aa9142018-12-03 21:07:23 +1100792 velocity_drivetrain = polydrivetrain.VelocityDrivetrainModel(
793 y2018.control_loops.python.drivetrain.kDrivetrain)
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100794 position_drivetrain = drivetrain.Drivetrain(
795 y2018.control_loops.python.drivetrain.kDrivetrain)
796
797 longitudal_accel = 3.0
798 lateral_accel = 2.0
799
800 trajectory = Trajectory(
801 path,
802 drivetrain=velocity_drivetrain,
803 longitudal_accel=longitudal_accel,
804 lateral_accel=lateral_accel,
805 distance_count=500)
Austin Schuh44aa9142018-12-03 21:07:23 +1100806
807 vmax = numpy.inf
808 vmax = 10.0
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100809 lateral_accel_plan = trajectory.lateral_accel_pass(
810 trajectory.create_plan(vmax))
Austin Schuh44aa9142018-12-03 21:07:23 +1100811
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100812 forward_accel_plan = lateral_accel_plan.copy()
813 # Start and end the path stopped.
814 forward_accel_plan[0] = 0.0
815 forward_accel_plan[-1] = 0.0
Austin Schuh44aa9142018-12-03 21:07:23 +1100816
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100817 forward_accel_plan = trajectory.forward_pass(forward_accel_plan)
Austin Schuh44aa9142018-12-03 21:07:23 +1100818
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100819 backward_accel_plan = trajectory.backward_pass(forward_accel_plan)
Austin Schuh44aa9142018-12-03 21:07:23 +1100820
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100821 # And now, calculate the left, right voltage as a function of distance.
Austin Schuh44aa9142018-12-03 21:07:23 +1100822
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100823 # TODO(austin): Factor out the accel and decel functions so we can use them
824 # to calculate voltage as a function of distance.
Austin Schuh44aa9142018-12-03 21:07:23 +1100825
826 pylab.figure()
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100827 pylab.plot(trajectory.distances, lateral_accel_plan, label='accel pass')
828 pylab.plot(trajectory.distances, forward_accel_plan, label='forward pass')
829 pylab.plot(trajectory.distances, backward_accel_plan, label='forward pass')
Austin Schuh44aa9142018-12-03 21:07:23 +1100830 pylab.xlabel("distance along spline (m)")
831 pylab.ylabel("velocity (m/s)")
832 pylab.legend()
833
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100834 voltages = numpy.matrix(numpy.zeros((2, len(distances))))
835 for i in range(len(distances)):
836 voltages[:, i] = trajectory.ff_voltage(backward_accel_plan,
837 distances[i])
838
839 # Now, let's integrate up the feed forwards voltage to see where we end up.
840 spline_state = numpy.matrix(numpy.zeros((4, 1)))
841 # x, y, theta, vl, vr
842 state = numpy.matrix(numpy.zeros((5, 1)))
843 dt = 0.005
844 num_timesteps = 400
845 spline_states = numpy.matrix(numpy.zeros((4, num_timesteps)))
846 states = numpy.matrix(numpy.zeros((5, num_timesteps)))
847 Us = numpy.matrix(numpy.zeros((2, num_timesteps)))
848 t = numpy.array(range(num_timesteps)) * dt
849 for i in range(num_timesteps):
850 spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
851 spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
852 def distance_spline_diffeq(t, x):
853 spline_distance = 0.5 * (x[0, 0] + x[2, 0])
854 spline_velocity = 0.5 * (x[1, 0] + x[3, 0])
855 U = trajectory.ff_voltage(
856 backward_accel_plan,
857 spline_distance)
858 dXdt = (position_drivetrain.A_continuous * x +
859 position_drivetrain.B_continuous * U)
860 return dXdt
861 spline_state = RungeKutta(distance_spline_diffeq, spline_state, i * dt, dt)
862
863 def spline_diffeq(t, x):
864 spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
865 spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
866 velocity = x[3:, :]
867 theta = x[2, 0]
868 linear_velocity = (velocity[0, 0] + velocity[1, 0]) / 2.0
869 angular_velocity = (velocity[1, 0] - velocity[0, 0]) / (
870 velocity_drivetrain.robot_radius_l +
871 velocity_drivetrain.robot_radius_r)
872 U = trajectory.ff_voltage(
873 backward_accel_plan,
874 spline_distance + numpy.linalg.norm(x[0:2, 0] - state[0:2, 0]))
875 accel = (velocity_drivetrain.A_continuous * velocity +
876 velocity_drivetrain.B_continuous * U)
877 return numpy.matrix(
878 [[numpy.cos(theta) * linear_velocity],
879 [numpy.sin(theta) * linear_velocity], [angular_velocity],
880 [accel[0, 0]], [accel[1, 0]]])
881
882 U = trajectory.ff_voltage(backward_accel_plan, spline_distance)
883 state = RungeKutta(spline_diffeq, state, i * dt, dt)
884 spline_states[:, i] = spline_state
885 states[:, i] = state
886 Us[:, i] = U
887
888 spline_distances = numpy.array(
889 (numpy.array(spline_states)[0, :] + numpy.array(spline_states)[2, :]) /
890 2.0)
891 pylab.figure()
892 pylab.plot(distances, numpy.array(voltages)[0, :], label='vl')
893 pylab.plot(distances, numpy.array(voltages)[1, :], label='vr')
894 pylab.legend()
895
896 pylab.figure()
897 pylab.plot(
898 numpy.array(spline_points)[0, :],
899 numpy.array(spline_points)[1, :],
900 label='spline')
901 pylab.plot(
902 numpy.array(states)[0, :], numpy.array(states)[1, :], label="robot")
903 pylab.legend()
904
905 pylab.figure()
906 pylab.plot(
907 spline_distances, (numpy.array(states)[0, :] - numpy.array(
908 path.xy(spline_distances))[0, :]) * 100.0,
909 label='robotx_error * 100')
910 pylab.plot(
911 spline_distances, (numpy.array(states)[1, :] - numpy.array(
912 path.xy(spline_distances))[1, :]) * 100.0,
913 label='roboty_error * 100')
914 pylab.plot(
915 spline_distances, (numpy.array(states)[2, :] - numpy.array(
916 path.theta(spline_distances))) * 100.0,
917 label='robottheta_error * 100')
918 pylab.plot(distances, numpy.array(voltages)[0, :], label='voltsl')
919 pylab.plot(distances, numpy.array(voltages)[1, :], label='voltsr')
920 pylab.legend()
921
922 def a(_, x):
923 return 2.0
924 return 2.0 + 0.0001 * x
925
926 v = 0.0
927 for _ in xrange(10):
928 dx = 4.0 / 10.0
929 v = integrate_accel_for_distance(a, v, 0.0, dx)
930 print('v', v)
931
Austin Schuh35d19872018-11-30 15:50:47 +1100932 pylab.show()
933
934
935if __name__ == '__main__':
936 argv = FLAGS(sys.argv)
937 sys.exit(main(argv))