blob: d045f45221249201696fa5ad2372b8f56a52df7a [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]]
Austin Schuhec7f06d2019-01-04 07:47:15 +1100494 maxa = -float('inf')
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100495 for a in accelerations:
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100496 U = K5 * a + K3 * v * v + K4 * v
497 if not (numpy.abs(U) > 12.0 + 1e-6).any():
498 maxa = max(maxa, a)
499
500 lateral_accel = v * v * numpy.linalg.norm(self._path.ddxy(x))
501 # Constrain the longitudinal acceleration to keep in a pseudo friction
502 # circle. This will make it so we don't floor it while in a turn and
503 # cause extra wheel slip.
504 long_accel = numpy.sqrt(1.0 - (lateral_accel / self._lateral_accel)**
505 2.0) * self._longitudal_accel
506 return min(long_accel, maxa)
507
508 def forward_pass(self, plan):
509 plan = plan.copy()
510 for i, distance in enumerate(self.distances):
511 if i == len(self.distances) - 1:
512 break
513
514 plan[i + 1] = min(
515 plan[i + 1],
516 integrate_accel_for_distance(
517 self.forward_acceleration, plan[i], self.distances[i],
518 self.distances[i + 1] - self.distances[i]))
519 return plan
520
521 def backward_acceleration(self, x, v):
522 # TODO(austin): Forwards and backwards are quite similar. Can we
523 # factor this out?
524 current_ddtheta = self._path.ddtheta(x)[0]
525 current_dtheta = self._path.dtheta(x)[0]
526 # We've now got the equation:
527 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
528 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
529 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
530
531 C = K3 * v * v + K4 * v
532 # Note: K345 are not quite constant over the step, but we are going
533 # to assume they are for now.
534 accelerations = [(12.0 - C[0, 0]) / K5[0, 0], (12.0 - C[1, 0]) /
535 K5[1, 0], (-12.0 - C[0, 0]) / K5[0, 0],
536 (-12.0 - C[1, 0]) / K5[1, 0]]
Austin Schuhec7f06d2019-01-04 07:47:15 +1100537 mina = float('inf')
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100538 for a in accelerations:
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100539 U = K5 * a + K3 * v * v + K4 * v
540 if not (numpy.abs(U) > 12.0 + 1e-6).any():
541 mina = min(mina, a)
542
543 lateral_accel = v * v * numpy.linalg.norm(self._path.ddxy(x))
544 # Constrain the longitudinal acceleration to keep in a pseudo friction
545 # circle. This will make it so we don't floor it while in a turn and
546 # cause extra wheel slip.
547 long_accel = -numpy.sqrt(1.0 - (lateral_accel / self._lateral_accel)**
548 2.0) * self._longitudal_accel
549 return max(long_accel, mina)
550
551 def backward_pass(self, plan):
552 plan = plan.copy()
553 for i, distance in reversed(list(enumerate(self.distances))):
554 if i == 0:
555 break
556
557 plan[i - 1] = min(
558 plan[i - 1],
559 integrate_accel_for_distance(
560 self.backward_acceleration, plan[i], self.distances[i],
561 self.distances[i - 1] - self.distances[i]))
562 return plan
563
564 # TODO(austin): The plan should probably not be passed in...
565 def ff_voltage(self, plan, distance):
566 if distance < self.distances[1]:
567 after_index = 1
568 before_index = after_index - 1
569 if distance < self.distances[0]:
570 distance = 0.0
571 elif distance > self.distances[-2]:
572 after_index = len(self.distances) - 1
573 before_index = after_index - 1
574 if distance > self.distances[-1]:
575 distance = self.distances[-1]
576 else:
577 after_index = numpy.searchsorted(
578 self.distances, distance, side='right')
579 before_index = after_index - 1
580
581 vforwards = integrate_accel_for_distance(
582 self.forward_acceleration, plan[before_index],
583 self.distances[before_index],
584 distance - self.distances[before_index])
585 vbackward = integrate_accel_for_distance(
586 self.backward_acceleration, plan[after_index],
587 self.distances[after_index],
588 distance - self.distances[after_index])
589
590 vcurvature = self.lateral_velocity_curvature(distance)
591
592 if vcurvature < vforwards and vcurvature < vbackward:
593 accel = 0
594 velocity = vcurvature
595 elif vforwards < vbackward:
596 velocity = vforwards
597 accel = self.forward_acceleration(distance, velocity)
598 else:
599 velocity = vbackward
600 accel = self.backward_acceleration(distance, velocity)
601
602 current_ddtheta = self._path.ddtheta(distance)[0]
603 current_dtheta = self._path.dtheta(distance)[0]
604 # TODO(austin): Factor these out.
605 # We've now got the equation:
606 # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
607 # Now, rephrase it as K5 a + K3 v^2 + K4 v = U
608 K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
609
610 U = K5 * accel + K3 * velocity * velocity + K4 * velocity
611 return U
Austin Schuh387a6872018-12-01 19:14:33 +1100612
Austin Schuh35d19872018-11-30 15:50:47 +1100613def main(argv):
614 # Build up the control point matrix
615 start = numpy.matrix([[0.0, 0.0]]).T
616 c1 = numpy.matrix([[0.5, 0.0]]).T
617 c2 = numpy.matrix([[0.5, 1.0]]).T
618 end = numpy.matrix([[1.0, 1.0]]).T
619 control_points = numpy.hstack((start, c1, c2, end))
620
621 # The alphas to plot
622 alphas = numpy.linspace(0.0, 1.0, 1000)
623
624 # Compute x, y and the 3 derivatives
625 spline_points = spline(alphas, control_points)
626 dspline_points = dspline(alphas, control_points)
627 ddspline_points = ddspline(alphas, control_points)
628 dddspline_points = dddspline(alphas, control_points)
629
630 # Compute theta and the two derivatives
631 theta = spline_theta(alphas, control_points, dspline_points=dspline_points)
Austin Schuh387a6872018-12-01 19:14:33 +1100632 dtheta = dspline_theta(
633 alphas, control_points, dspline_points=dspline_points)
Austin Schuh35d19872018-11-30 15:50:47 +1100634 ddtheta = ddspline_theta(
635 alphas,
636 control_points,
637 dspline_points=dspline_points,
638 dddspline_points=dddspline_points)
639
640 # Plot the control points and the spline.
641 pylab.figure()
642 pylab.plot(
643 numpy.array(control_points)[0, :],
644 numpy.array(control_points)[1, :],
645 '-o',
646 label='control')
647 pylab.plot(
648 numpy.array(spline_points)[0, :],
649 numpy.array(spline_points)[1, :],
650 label='spline')
651 pylab.legend()
652
653 # For grins, confirm that the double integral of the acceleration (with
654 # respect to the spline parameter) matches the position. This lets us
655 # confirm that the derivatives are consistent.
656 xint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
657 dxint_plot = xint_plot.copy()
658 xint = spline_points[:, 0].copy()
659 dxint = dspline_points[:, 0].copy()
660 xint_plot[:, 0] = xint
661 dxint_plot[:, 0] = dxint
662 for i in range(len(alphas) - 1):
663 xint += (alphas[i + 1] - alphas[i]) * dxint
664 dxint += (alphas[i + 1] - alphas[i]) * ddspline_points[:, i]
665 xint_plot[:, i + 1] = xint
666 dxint_plot[:, i + 1] = dxint
667
668 # Integrate up the spline velocity and heading to confirm that given a
669 # velocity (as a function of the spline parameter) and angle, we will move
670 # from the starting point to the ending point.
671 thetaint_plot = numpy.zeros((len(alphas),))
672 thetaint = theta[0]
673 dthetaint_plot = numpy.zeros((len(alphas),))
674 dthetaint = dtheta[0]
675 thetaint_plot[0] = thetaint
676 dthetaint_plot[0] = dthetaint
677
678 txint_plot = numpy.matrix(numpy.zeros((2, len(alphas))))
679 txint = spline_points[:, 0].copy()
680 txint_plot[:, 0] = txint
681 for i in range(len(alphas) - 1):
682 dalpha = alphas[i + 1] - alphas[i]
683 txint += dalpha * numpy.linalg.norm(
684 dspline_points[:, i]) * numpy.matrix(
685 [[numpy.cos(theta[i])], [numpy.sin(theta[i])]])
686 txint_plot[:, i + 1] = txint
687 thetaint += dalpha * dtheta[i]
688 dthetaint += dalpha * ddtheta[i]
689 thetaint_plot[i + 1] = thetaint
690 dthetaint_plot[i + 1] = dthetaint
691
692
693 # Now plot x, dx/dalpha, ddx/ddalpha, dddx/dddalpha, and integrals thereof
694 # to perform consistency checks.
695 pylab.figure()
696 pylab.plot(alphas, numpy.array(spline_points)[0, :], label='x')
697 pylab.plot(alphas, numpy.array(xint_plot)[0, :], label='ix')
698 pylab.plot(alphas, numpy.array(dspline_points)[0, :], label='dx')
699 pylab.plot(alphas, numpy.array(dxint_plot)[0, :], label='idx')
700 pylab.plot(alphas, numpy.array(txint_plot)[0, :], label='tix')
701 pylab.plot(alphas, numpy.array(ddspline_points)[0, :], label='ddx')
702 pylab.plot(alphas, numpy.array(dddspline_points)[0, :], label='dddx')
703 pylab.legend()
704
705 # Now do the same for y.
706 pylab.figure()
707 pylab.plot(alphas, numpy.array(spline_points)[1, :], label='y')
708 pylab.plot(alphas, numpy.array(xint_plot)[1, :], label='iy')
709 pylab.plot(alphas, numpy.array(dspline_points)[1, :], label='dy')
710 pylab.plot(alphas, numpy.array(dxint_plot)[1, :], label='idy')
711 pylab.plot(alphas, numpy.array(txint_plot)[1, :], label='tiy')
712 pylab.plot(alphas, numpy.array(ddspline_points)[1, :], label='ddy')
713 pylab.plot(alphas, numpy.array(dddspline_points)[1, :], label='dddy')
714 pylab.legend()
715
716 # And for theta.
717 pylab.figure()
718 pylab.plot(alphas, theta, label='theta')
719 pylab.plot(alphas, dtheta, label='dtheta')
720 pylab.plot(alphas, ddtheta, label='ddtheta')
721 pylab.plot(alphas, thetaint_plot, label='thetai')
722 pylab.plot(alphas, dthetaint_plot, label='dthetai')
Austin Schuh387a6872018-12-01 19:14:33 +1100723 pylab.plot(
724 alphas,
725 numpy.linalg.norm(
726 numpy.array(dspline_points), axis=0),
727 label='velocity')
728
729 # Now, repeat as a function of path length as opposed to alpha
730 path = Path(control_points)
731 distance_count = 1000
732 position = path.xy(0.0)
733 velocity = path.dxy(0.0)
734 theta = path.theta(0.0)
735 omega = path.dtheta(0.0)
736
737 iposition_plot = numpy.matrix(numpy.zeros((2, distance_count)))
738 ivelocity_plot = numpy.matrix(numpy.zeros((2, distance_count)))
739 iposition_plot[:, 0] = position.copy()
740 ivelocity_plot[:, 0] = velocity.copy()
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100741 itheta_plot = numpy.zeros((distance_count, ))
742 iomega_plot = numpy.zeros((distance_count, ))
Austin Schuh387a6872018-12-01 19:14:33 +1100743 itheta_plot[0] = theta
744 iomega_plot[0] = omega
745
746 distances = numpy.linspace(0.0, path.length(), distance_count)
747
748 for i in xrange(len(distances) - 1):
749 position += velocity * (distances[i + 1] - distances[i])
750 velocity += path.ddxy(distances[i]) * (distances[i + 1] - distances[i])
751 iposition_plot[:, i + 1] = position
752 ivelocity_plot[:, i + 1] = velocity
753
754 theta += omega * (distances[i + 1] - distances[i])
755 omega += path.ddtheta(distances[i]) * (distances[i + 1] - distances[i])
756 itheta_plot[i + 1] = theta
757 iomega_plot[i + 1] = omega
758
759 pylab.figure()
760 pylab.plot(distances, numpy.array(path.xy(distances))[0, :], label='x')
761 pylab.plot(distances, numpy.array(iposition_plot)[0, :], label='ix')
762 pylab.plot(distances, numpy.array(path.dxy(distances))[0, :], label='dx')
763 pylab.plot(distances, numpy.array(ivelocity_plot)[0, :], label='idx')
764 pylab.plot(distances, numpy.array(path.ddxy(distances))[0, :], label='ddx')
765 pylab.legend()
766
767 pylab.figure()
768 pylab.plot(distances, numpy.array(path.xy(distances))[1, :], label='y')
769 pylab.plot(distances, numpy.array(iposition_plot)[1, :], label='iy')
770 pylab.plot(distances, numpy.array(path.dxy(distances))[1, :], label='dy')
771 pylab.plot(distances, numpy.array(ivelocity_plot)[1, :], label='idy')
772 pylab.plot(distances, numpy.array(path.ddxy(distances))[1, :], label='ddy')
773 pylab.legend()
774
775 pylab.figure()
776 pylab.plot(distances, path.theta(distances), label='theta')
777 pylab.plot(distances, itheta_plot, label='itheta')
778 pylab.plot(distances, path.dtheta(distances), label='omega')
779 pylab.plot(distances, iomega_plot, label='iomega')
780 pylab.plot(distances, path.ddtheta(distances), label='alpha')
781 pylab.legend()
Austin Schuh35d19872018-11-30 15:50:47 +1100782
783 # TODO(austin): Start creating a velocity plan now that we have all the
784 # derivitives of our spline.
785
Austin Schuh44aa9142018-12-03 21:07:23 +1100786 velocity_drivetrain = polydrivetrain.VelocityDrivetrainModel(
787 y2018.control_loops.python.drivetrain.kDrivetrain)
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100788 position_drivetrain = drivetrain.Drivetrain(
789 y2018.control_loops.python.drivetrain.kDrivetrain)
790
791 longitudal_accel = 3.0
792 lateral_accel = 2.0
793
794 trajectory = Trajectory(
795 path,
796 drivetrain=velocity_drivetrain,
797 longitudal_accel=longitudal_accel,
798 lateral_accel=lateral_accel,
799 distance_count=500)
Austin Schuh44aa9142018-12-03 21:07:23 +1100800
801 vmax = numpy.inf
802 vmax = 10.0
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100803 lateral_accel_plan = trajectory.lateral_accel_pass(
804 trajectory.create_plan(vmax))
Austin Schuh44aa9142018-12-03 21:07:23 +1100805
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100806 forward_accel_plan = lateral_accel_plan.copy()
807 # Start and end the path stopped.
808 forward_accel_plan[0] = 0.0
809 forward_accel_plan[-1] = 0.0
Austin Schuh44aa9142018-12-03 21:07:23 +1100810
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100811 forward_accel_plan = trajectory.forward_pass(forward_accel_plan)
Austin Schuh44aa9142018-12-03 21:07:23 +1100812
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100813 backward_accel_plan = trajectory.backward_pass(forward_accel_plan)
Austin Schuh44aa9142018-12-03 21:07:23 +1100814
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100815 # And now, calculate the left, right voltage as a function of distance.
Austin Schuh44aa9142018-12-03 21:07:23 +1100816
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100817 # TODO(austin): Factor out the accel and decel functions so we can use them
818 # to calculate voltage as a function of distance.
Austin Schuh44aa9142018-12-03 21:07:23 +1100819
820 pylab.figure()
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100821 pylab.plot(trajectory.distances, lateral_accel_plan, label='accel pass')
822 pylab.plot(trajectory.distances, forward_accel_plan, label='forward pass')
823 pylab.plot(trajectory.distances, backward_accel_plan, label='forward pass')
Austin Schuh44aa9142018-12-03 21:07:23 +1100824 pylab.xlabel("distance along spline (m)")
825 pylab.ylabel("velocity (m/s)")
826 pylab.legend()
827
Austin Schuh8cb98eb2018-12-05 22:06:45 +1100828 voltages = numpy.matrix(numpy.zeros((2, len(distances))))
829 for i in range(len(distances)):
830 voltages[:, i] = trajectory.ff_voltage(backward_accel_plan,
831 distances[i])
832
833 # Now, let's integrate up the feed forwards voltage to see where we end up.
834 spline_state = numpy.matrix(numpy.zeros((4, 1)))
835 # x, y, theta, vl, vr
836 state = numpy.matrix(numpy.zeros((5, 1)))
837 dt = 0.005
838 num_timesteps = 400
839 spline_states = numpy.matrix(numpy.zeros((4, num_timesteps)))
840 states = numpy.matrix(numpy.zeros((5, num_timesteps)))
841 Us = numpy.matrix(numpy.zeros((2, num_timesteps)))
842 t = numpy.array(range(num_timesteps)) * dt
843 for i in range(num_timesteps):
844 spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
845 spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
846 def distance_spline_diffeq(t, x):
847 spline_distance = 0.5 * (x[0, 0] + x[2, 0])
848 spline_velocity = 0.5 * (x[1, 0] + x[3, 0])
849 U = trajectory.ff_voltage(
850 backward_accel_plan,
851 spline_distance)
852 dXdt = (position_drivetrain.A_continuous * x +
853 position_drivetrain.B_continuous * U)
854 return dXdt
855 spline_state = RungeKutta(distance_spline_diffeq, spline_state, i * dt, dt)
856
857 def spline_diffeq(t, x):
858 spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
859 spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
860 velocity = x[3:, :]
861 theta = x[2, 0]
862 linear_velocity = (velocity[0, 0] + velocity[1, 0]) / 2.0
863 angular_velocity = (velocity[1, 0] - velocity[0, 0]) / (
864 velocity_drivetrain.robot_radius_l +
865 velocity_drivetrain.robot_radius_r)
866 U = trajectory.ff_voltage(
867 backward_accel_plan,
868 spline_distance + numpy.linalg.norm(x[0:2, 0] - state[0:2, 0]))
869 accel = (velocity_drivetrain.A_continuous * velocity +
870 velocity_drivetrain.B_continuous * U)
871 return numpy.matrix(
872 [[numpy.cos(theta) * linear_velocity],
873 [numpy.sin(theta) * linear_velocity], [angular_velocity],
874 [accel[0, 0]], [accel[1, 0]]])
875
876 U = trajectory.ff_voltage(backward_accel_plan, spline_distance)
877 state = RungeKutta(spline_diffeq, state, i * dt, dt)
878 spline_states[:, i] = spline_state
879 states[:, i] = state
880 Us[:, i] = U
881
882 spline_distances = numpy.array(
883 (numpy.array(spline_states)[0, :] + numpy.array(spline_states)[2, :]) /
884 2.0)
885 pylab.figure()
886 pylab.plot(distances, numpy.array(voltages)[0, :], label='vl')
887 pylab.plot(distances, numpy.array(voltages)[1, :], label='vr')
888 pylab.legend()
889
890 pylab.figure()
891 pylab.plot(
892 numpy.array(spline_points)[0, :],
893 numpy.array(spline_points)[1, :],
894 label='spline')
895 pylab.plot(
896 numpy.array(states)[0, :], numpy.array(states)[1, :], label="robot")
897 pylab.legend()
898
899 pylab.figure()
900 pylab.plot(
901 spline_distances, (numpy.array(states)[0, :] - numpy.array(
902 path.xy(spline_distances))[0, :]) * 100.0,
903 label='robotx_error * 100')
904 pylab.plot(
905 spline_distances, (numpy.array(states)[1, :] - numpy.array(
906 path.xy(spline_distances))[1, :]) * 100.0,
907 label='roboty_error * 100')
908 pylab.plot(
909 spline_distances, (numpy.array(states)[2, :] - numpy.array(
910 path.theta(spline_distances))) * 100.0,
911 label='robottheta_error * 100')
912 pylab.plot(distances, numpy.array(voltages)[0, :], label='voltsl')
913 pylab.plot(distances, numpy.array(voltages)[1, :], label='voltsr')
914 pylab.legend()
915
916 def a(_, x):
917 return 2.0
918 return 2.0 + 0.0001 * x
919
920 v = 0.0
921 for _ in xrange(10):
922 dx = 4.0 / 10.0
923 v = integrate_accel_for_distance(a, v, 0.0, dx)
924 print('v', v)
925
Austin Schuh35d19872018-11-30 15:50:47 +1100926 pylab.show()
927
928
929if __name__ == '__main__':
930 argv = FLAGS(sys.argv)
931 sys.exit(main(argv))