blob: 4376f3739ae43b60cb34d5e772c3e04b36ebcd0b [file] [log] [blame]
John Park1aaadc72018-11-04 13:13:08 -08001#!/usr/bin/python
2import numpy as np
3import matplotlib.pyplot as plt
4from frc971.control_loops.python import drivetrain
5
6# used to define properties of the drivetrain, changes depending on robot
7# see yXXXX/control_loops/python/drivetrain.py for the current values
8
9kDrivetrain = drivetrain.DrivetrainParams(
10 J = 6.0,
11 mass=68.0,
12 robot_radius=0.616 / 2.0,
13 wheel_radius=0.127 / 2.0 * 120.0 / 118.0,
14 G_low=46.0 / 60.0 * 20.0 / 48.0 * 14.0 / 62.0,
15 G_high=62.0 / 44.0 * 20.0 / 48.0 * 14.0 / 62.0,
16 q_pos_low=0.12,
17 q_pos_high=0.14,
18 q_vel_low=1.0,
19 q_vel_high=0.95,
20 efficiency=0.70,
21 has_imu=True,
22 force=True,
23 kf_q_voltage=13.0,
24 controller_poles=[0.82, 0.82],
25)
26
27drivetrain = drivetrain.Drivetrain(kDrivetrain)
28# set up coefficients for Hemrite basis function evaluation
29coeffs = np.array([[1, 0, 0, -10, 15, -6], [0, 1, 0, -6, 8, -3], [0, 0, 0.5, -1.5, 1.5, -0.5], [0, 0, 0, 0.5, -1, 0.5], [0, 0, 0, -4, 7, -3], [0, 0, 0, 10, -15, 6]])
30coeffs_prime = np.empty_like(coeffs)
31for ii in range(0, len(coeffs)):
32 for jj in range(0, len(coeffs[ii]) - 1):
33 coeffs_prime[ii][jj] = (jj + 1) * coeffs[ii][jj]
34
35def RungeKutta(f, x, dt):
36 """4th order RungeKutta integration of F starting at X."""
37 a = f(x)
38 b = f(x + dt / 2.0 * a)
39 c = f(x + dt / 2.0 * b)
40 d = f(x + dt * c)
41
42 return x + dt * (a + 2.0 * b + 2.0 * c + d) / 6.0
43
44def normalize(v):
45 norm = np.linalg.norm(v)
46 return v / norm
47
48def theta(v):
49 return np.arctan2(v[1], v[0])
50
51# evaluate Nth hermite basis function at t
52def nth_H(N, t):
53 return coeffs[N][0] + coeffs[N][1]*t + coeffs[N][2]*t**2 + coeffs[N][3]*t**3 + coeffs[N][4]*t**4 + coeffs[N][5]*t**5
54
55def nth_H_prime(N, t):
56 return coeffs[N][0] + coeffs[N][1]*t + coeffs[N][2]*t**2 + coeffs[N][3]*t**3 + coeffs[N][4]*t**4
57
58# class defining a quintic Hermite spline, with utilities for modification and plotting
59class Hermite_Spline:
60 # init method given known parameters, ie savefile loading(if necessary)
61 def __init__(self, start, control1, control2, end, resolution = 200):
62 self.start = start
63 self.end = end
64 self.control1 = control1
65 self.control2 = control2
66
67 self.points = np.array([])
68 self.velocities = []
69 self.accelerations = []
70 self.arc_lengths = []
71 self.thetas = []
72 self.omegas = []
73 self.curvatures = []
74
75 self.shifted_points = []
76
77 self.Ks = []
78 self.dKs = []
79
80 # coefficients are po, v0, a0, a1, v1, p1
81 self.coeffs = np.array([])
82 self.compute_coefficients()
83 self.resolution = resolution
84 self.setup()
85
86 # take paramters and compute coeffcicents for Hermite basis functions, to be called every time he change control points
87 def compute_coefficients(self):
88 self.coeffs = np.append(self.coeffs, np.array(self.start))
89 self.coeffs = np.append(self.coeffs, np.array(self.control1) - np.array(self.start))
90 self.coeffs = np.append(self.coeffs, [0,0])
91 self.coeffs = np.append(self.coeffs, [0,0])
92 self.coeffs = np.append(self.coeffs, np.array(self.end) - np.array(self.control2))
93 self.coeffs = np.append(self.coeffs, np.array(self.end))
94
95 self.coeffs = np.reshape(self.coeffs, newshape = (6, 2))
96
97 # setters for control points, set coefficients
98 def set_positions(self, p1 = None, p2 = None):
99 if p1 != None:
100 self.start = p1
101 if p2 != None:
102 self.end = p2
103
104 self.compute_coefficients()
105
106 def set_controls(self, c1 = None, c2 = None):
107 if c1 != None:
108 self.control1 = c1
109 if c2 != None:
110 self.control2 = c2
111
112 self.compute_coefficients()
113
114 def set_velocities(self, v1 = None, v2 = None):
115 if v1 != None:
116 self.control1 = self.start + v1
117 if v2 != None:
118 self.control2 = self.end + v2
119
120 self.compute_coefficients()
121
122 def get_smoothness(self):
123 K = self.get_curvature()
124 return np.sum(np.abs(np.gradient(K)))
125
126 # given Basis functions and controls compute coordinate given t
127 def spline_eval_hermite(self, t):
128 return np.array(self.coeffs[0]*nth_H(0, t) + self.coeffs[1]*nth_H(1, t)+ self.coeffs[2]*nth_H(2, t) + self.coeffs[3]*nth_H(3, t) + self.coeffs[4]* nth_H(4, t)+ self.coeffs[5]*nth_H(5, t))
129
130 # given Basis functions and controls compute velocity given t
131 def spline_eval_hermite_v(self, t):
132 return normalize(np.array(self.coeffs[0]*nth_H_prime(0, t) + self.coeffs[1]*nth_H_prime(1, t)+ self.coeffs[2]*nth_H_prime(2, t) + self.coeffs[3]*nth_H_prime(3, t) + self.coeffs[4]* nth_H_prime(4, t)+ self.coeffs[5]*nth_H_prime(5, t)))
133
134 # take coefficients and compute spline points/properties
135 def setup(self, resolution_multiplier = 10, dt = .000001):
136 points = []
137 velocities = []
138 accelerations = []
139 s = []
140 thetas = []
141 omegas = []
142 curvatures = []
143
144 last_point = self.spline_eval_hermite(0)
145 distance = 0
146
147 # iterate through interim points and compute pos_vectors, and at predefined points arc length,
148 # velocity, and acceleration vectors and store them at their associated index
149 for i in range(0, self.resolution * resolution_multiplier):
150 t = i / (1.0 * self.resolution * resolution_multiplier)
151
152 current_point = self.spline_eval_hermite(t)
153 current_point_dt = self.spline_eval_hermite(t + dt)
154 current_s = np.linalg.norm(current_point - last_point)
155
156 ds = np.linalg.norm(current_point_dt - current_point)
157
158 distance = current_s + distance
159 # at important points compute important values and store
160 if i % resolution_multiplier == 0:
161 s.append(distance)
162 points.append(current_point)
163
164 v = self.spline_eval_hermite_v(t)
165 v_dt = self.spline_eval_hermite_v(t + dt)
166 theta_t = theta(v)
167 theta_dt = theta(v_dt)
168
169 a = (v_dt - v) / ds
170 omega = (theta_dt - theta_t) / ds
171 if np.linalg.norm(v) == 0:
172 curvature = 0
173 else:
174 curvature = np.linalg.det(np.column_stack((v, a)) / (np.linalg.norm(v)**(3/2)))
175
176 velocities.append(v)
177 accelerations.append(a)
178 thetas.append(theta_t)
179 omegas.append(omega)
180 if curvature == 0:
181 curvatures.append(0.0001)
182 else:
183 curvatures.append(curvature)
184
185 last_point = current_point
186
187 self.arc_lengths = np.array(s)
188 self.points = np.reshape(points, newshape = (-1, 2))
189 self.velocities = np.reshape(velocities, newshape = (-1, 2))
190 self.accelerations = np.reshape(accelerations, newshape = (-1, 2))
191 self.thetas = np.array(thetas)
192 self.omegas = np.array(omegas)
193 self.curvatures = np.array(curvatures)
194
195
196 def plot_diagnostics(self):
197 plt.figure("Spline")
198 plt.title('Spline')
199 plt.plot(self.points[:, 0], self.points[:, 1])
200 # plt.scatter(self.points[:, 0], self.points[:, 1])
201
202 plt.figure("Diagnostics")
203
204 plt.subplot(2, 2, 1)
205 plt.title('theta')
206 plt.xlabel('arc_length')
207 plt.ylabel('theta')
208 theta, = plt.plot(self.arc_lengths, self.thetas, label = 'theta')
209 plt.legend(handles = [theta])
210
211 plt.subplot(2, 2, 2)
212 plt.title('omegas')
213 plt.xlabel('arc_length')
214 plt.ylabel('omega')
215 omega, = plt.plot(self.arc_lengths, self.omegas, label = 'omega')
216 plt.legend(handles = [omega])
217
218 plt.subplot(2, 2, 3)
219 plt.title('Velocities')
220 plt.xlabel('arc_length')
221 plt.ylabel('velocity')
222 dxds, = plt.plot(self.arc_lengths, self.velocities[:, 0], label = 'dx/ds')
223 dyds, = plt.plot(self.arc_lengths, self.velocities[:, 1], label = 'dy/ds')
224 plt.legend(handles = [dxds, dyds])
225
226 plt.subplot(2, 2, 4)
227 plt.title('Accelerations')
228 plt.xlabel('arc_length')
229 plt.ylabel('acceleration')
230 dx2ds2, = plt.plot(self.arc_lengths, self.accelerations[:, 0], label = 'd^2x/ds^2')
231 dy2ds2, = plt.plot(self.arc_lengths, self.accelerations[:, 1], label = 'd^2x/ds^2')
232 plt.legend(handles = [dx2ds2, dy2ds2])
233
234# class defining a number of splines with convinience methods
235class Path:
236 def __init__(self):
237 self.splines = []
238 self.knot_accels = []
239
240 def add_spline(self, spline):
241 self.splines.append(spline)
242
243 def get_K(self):
244 curvatures = []
245 for spline in self.splines:
246 curvatures.append(spline.curvatures)
247 return np.array(curvatures).flatten()
248
249 def get_S(self):
250 arc_lengths = []
251 for spline in self.splines:
252 arc_lengths.append(spline.arc_lengths)
253 return np.array(arc_lengths).flatten()
254
255 def get_points(self):
256 points = []
257 for spline in self.splines:
258 points.append(spline.points)
259 return points
260
261 def get_velocities(self, i):
262 velocities = []
263 for spline in self.splines:
264 velocities.append(spline.points)
265 return velocities
266
267 def remove_spine(self, i):
268 if i < len(self.splines):
269 self.splines.pop(i)
270 else:
271 print("index %f out of bounds, no spline of that index" % i)
272
273 def join(self, first_priority = False):
274 for i in range(0, len(self.splines)):
275 if first_priority & i != len(self.splines):
276 print("unfinished")
277
278
279# class which takes a Path object along with constraints and reparamterizes it with respect to time
280class Trajectory:
281 def __init__(self, path, max_angular_accel=3, max_voltage=11, max_normal_accel = .2):
282 self.path = path
283 self.A = drivetrain.A_continuous
284 self.B = drivetrain.B_continuous
285 self.robot_radius = drivetrain.robot_radius
286 self.Kv = 100
287 self.robot_radius = 3
288 self.max_angular_accel = max_angular_accel
289 self.max_voltage = max_voltage
290 self.max_normal_accel = max_normal_accel
291
292 self.max_velocities_adhering_to_normal_accel = []
293 self.max_velocities_adhering_to_voltage = []
294 self.path.splines[0].setup(resolution_multiplier = 100)
295
296 self.set_max_v_adhering_to_normal_accel()
297 self.max_voltageK_pass()
298
299 def set_max_v_adhering_to_normal_accel(self):
300 Ks = self.path.get_K()
301 accels = np.full_like(Ks, fill_value = self.max_normal_accel)
302 max_velocities = np.sqrt(np.abs(accels / Ks))
303 self.max_velocities_adhering_to_normal_accel = max_velocities
304
305 def max_voltageK_pass(self):
306 max_ds_dt = []
307 Ks = self.path.get_K()
308 turning_radii = np.full_like(Ks, fill_value = 1) / np.abs(Ks)
309
310
311
312 # compute max steady-state velocity given voltage constraints
313 for i in range(0, len(Ks)):
314 v_ratio = (turning_radii[i] + self.robot_radius) / (turning_radii[i] - self.robot_radius)
315 matrix = np.array([[self.A[1, 1], self.A[1, 3], self.B[1, 1]], [self.A[3, 1] - 1, self.A[3, 3], self.B[3, 1]], [-1, v_ratio, 0]])
316 sols = np.array([-1 * self.max_voltage * self.B[1, 0], -1 * self.max_voltage * self.B[3, 0], 0])
317 Vs = np.dot(np.linalg.inv(matrix), sols)
318 max_ds_dt.append((Vs[0] + Vs[1]) / 2)
319
320 self.max_velocities_adhering_to_voltage = max_ds_dt
321 # compute the maximum acceleration we can ask for given voltage and, ya know, staying on the path.
322
323
324 '''
325 These methods use the continuous form of our drivetrain state equation
326 in order to compute the maximum acceleration which adheres to the path
327 and voltage constraints, as well as any arbitary set of constraints
328 on velocity as a function of arc_length
329 '''
330
331 def forward_accel_pass(self):
332 points = self.path.get_points()
333 velocities = self.path.get_velocities()
334 curvatures = self.path.get_K()
335 arc_lenghts = self.path.get_S()
336
337 for i in range(0, len(points)):
338 Xn1 =
339
340
341 def backward_accelaration_pass(self):
342
343 print("max backward accel pass")
344
345
346 def plot_diagnostics(self, i = 0):
347
348 plt.figure('max velocity')
349 plt.title('max_v_normal_accel')
350 plt.xlabel('arc_length')
351 plt.ylabel('max V')
352 max_v_normal = plt.plot(self.path.get_S(), self.max_velocities_adhering_to_normal_accel, label = 'ds/dt (normal)')# , label = 'ds/dt')
353 curvature = plt.plot(self.path.get_S(), 1000 * np.abs(self.path.get_K()), label = 'K')
354 max_v_K_V = plt.plot(self.path.get_S(), self.max_velocities_adhering_to_voltage, label = 'ds/dt (voltage)')
355 plt.legend(handles = [max_v_normal[0], curvature[0], max_v_K_V[0]])
356
357def main():
358 A = Hermite_Spline(np.array([0,0]), np.array([0,400]), np.array([200,300]), np.array([200,200]), resolution = 200)
359 A.plot_diagnostics()
360 path = Path()
361 path.add_spline(A)
362 trajectory = Trajectory(path, 0)
363 trajectory.plot_diagnostics()
364 plt.show()
365
366main()