blob: 0dc5804754fb7e36beed2e35837d27bbe000fe01 [file] [log] [blame]
Austin Schuh085eab92020-11-26 13:54:51 -08001#!/usr/bin/python3
John Park1aaadc72018-11-04 13:13:08 -08002import 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(
Ravago Jones5127ccc2022-07-31 16:32:45 -070010 J=6.0,
John Park1aaadc72018-11-04 13:13:08 -080011 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
Ravago Jones5127ccc2022-07-31 16:32:45 -070029coeffs = np.array([[1, 0, 0, -10, 15, -6], [0, 1, 0, -6, 8, -3],
30 [0, 0, 0.5, -1.5, 1.5, -0.5], [0, 0, 0, 0.5, -1, 0.5],
31 [0, 0, 0, -4, 7, -3], [0, 0, 0, 10, -15, 6]])
John Park1aaadc72018-11-04 13:13:08 -080032coeffs_prime = np.empty_like(coeffs)
33for ii in range(0, len(coeffs)):
34 for jj in range(0, len(coeffs[ii]) - 1):
35 coeffs_prime[ii][jj] = (jj + 1) * coeffs[ii][jj]
36
Ravago Jones5127ccc2022-07-31 16:32:45 -070037
John Park1aaadc72018-11-04 13:13:08 -080038def RungeKutta(f, x, dt):
39 """4th order RungeKutta integration of F starting at X."""
40 a = f(x)
41 b = f(x + dt / 2.0 * a)
42 c = f(x + dt / 2.0 * b)
43 d = f(x + dt * c)
44
45 return x + dt * (a + 2.0 * b + 2.0 * c + d) / 6.0
46
Ravago Jones5127ccc2022-07-31 16:32:45 -070047
John Park1aaadc72018-11-04 13:13:08 -080048def normalize(v):
49 norm = np.linalg.norm(v)
50 return v / norm
51
Ravago Jones5127ccc2022-07-31 16:32:45 -070052
John Park1aaadc72018-11-04 13:13:08 -080053def theta(v):
54 return np.arctan2(v[1], v[0])
55
Ravago Jones5127ccc2022-07-31 16:32:45 -070056
John Park1aaadc72018-11-04 13:13:08 -080057# evaluate Nth hermite basis function at t
58def nth_H(N, t):
Ravago Jones5127ccc2022-07-31 16:32:45 -070059 return coeffs[N][0] + coeffs[N][1] * t + coeffs[N][2] * t**2 + coeffs[N][
60 3] * t**3 + coeffs[N][4] * t**4 + coeffs[N][5] * t**5
61
John Park1aaadc72018-11-04 13:13:08 -080062
63def nth_H_prime(N, t):
Ravago Jones5127ccc2022-07-31 16:32:45 -070064 return coeffs[N][0] + coeffs[N][1] * t + coeffs[N][2] * t**2 + coeffs[N][
65 3] * t**3 + coeffs[N][4] * t**4
66
John Park1aaadc72018-11-04 13:13:08 -080067
68# class defining a quintic Hermite spline, with utilities for modification and plotting
69class Hermite_Spline:
70 # init method given known parameters, ie savefile loading(if necessary)
Ravago Jones5127ccc2022-07-31 16:32:45 -070071 def __init__(self, start, control1, control2, end, resolution=200):
John Park1aaadc72018-11-04 13:13:08 -080072 self.start = start
73 self.end = end
74 self.control1 = control1
75 self.control2 = control2
76
77 self.points = np.array([])
78 self.velocities = []
79 self.accelerations = []
80 self.arc_lengths = []
81 self.thetas = []
82 self.omegas = []
83 self.curvatures = []
84
85 self.shifted_points = []
86
87 self.Ks = []
88 self.dKs = []
89
90 # coefficients are po, v0, a0, a1, v1, p1
91 self.coeffs = np.array([])
92 self.compute_coefficients()
93 self.resolution = resolution
94 self.setup()
95
96 # take paramters and compute coeffcicents for Hermite basis functions, to be called every time he change control points
97 def compute_coefficients(self):
98 self.coeffs = np.append(self.coeffs, np.array(self.start))
Ravago Jones5127ccc2022-07-31 16:32:45 -070099 self.coeffs = np.append(self.coeffs,
100 np.array(self.control1) - np.array(self.start))
101 self.coeffs = np.append(self.coeffs, [0, 0])
102 self.coeffs = np.append(self.coeffs, [0, 0])
103 self.coeffs = np.append(self.coeffs,
104 np.array(self.end) - np.array(self.control2))
John Park1aaadc72018-11-04 13:13:08 -0800105 self.coeffs = np.append(self.coeffs, np.array(self.end))
106
Ravago Jones5127ccc2022-07-31 16:32:45 -0700107 self.coeffs = np.reshape(self.coeffs, newshape=(6, 2))
John Park1aaadc72018-11-04 13:13:08 -0800108
109 # setters for control points, set coefficients
Ravago Jones5127ccc2022-07-31 16:32:45 -0700110 def set_positions(self, p1=None, p2=None):
John Park1aaadc72018-11-04 13:13:08 -0800111 if p1 != None:
112 self.start = p1
113 if p2 != None:
114 self.end = p2
115
116 self.compute_coefficients()
117
Ravago Jones5127ccc2022-07-31 16:32:45 -0700118 def set_controls(self, c1=None, c2=None):
John Park1aaadc72018-11-04 13:13:08 -0800119 if c1 != None:
120 self.control1 = c1
121 if c2 != None:
122 self.control2 = c2
123
124 self.compute_coefficients()
125
Ravago Jones5127ccc2022-07-31 16:32:45 -0700126 def set_velocities(self, v1=None, v2=None):
John Park1aaadc72018-11-04 13:13:08 -0800127 if v1 != None:
128 self.control1 = self.start + v1
129 if v2 != None:
130 self.control2 = self.end + v2
Ravago Jones5127ccc2022-07-31 16:32:45 -0700131
John Park1aaadc72018-11-04 13:13:08 -0800132 self.compute_coefficients()
133
134 def get_smoothness(self):
135 K = self.get_curvature()
136 return np.sum(np.abs(np.gradient(K)))
137
138 # given Basis functions and controls compute coordinate given t
139 def spline_eval_hermite(self, t):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700140 return np.array(self.coeffs[0] * nth_H(0, t) +
141 self.coeffs[1] * nth_H(1, t) +
142 self.coeffs[2] * nth_H(2, t) +
143 self.coeffs[3] * nth_H(3, t) +
144 self.coeffs[4] * nth_H(4, t) +
145 self.coeffs[5] * nth_H(5, t))
146
John Park1aaadc72018-11-04 13:13:08 -0800147 # given Basis functions and controls compute velocity given t
148 def spline_eval_hermite_v(self, t):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700149 return normalize(
150 np.array(self.coeffs[0] * nth_H_prime(0, t) +
151 self.coeffs[1] * nth_H_prime(1, t) +
152 self.coeffs[2] * nth_H_prime(2, t) +
153 self.coeffs[3] * nth_H_prime(3, t) +
154 self.coeffs[4] * nth_H_prime(4, t) +
155 self.coeffs[5] * nth_H_prime(5, t)))
John Park1aaadc72018-11-04 13:13:08 -0800156
157 # take coefficients and compute spline points/properties
Ravago Jones5127ccc2022-07-31 16:32:45 -0700158 def setup(self, resolution_multiplier=10, dt=.000001):
John Park1aaadc72018-11-04 13:13:08 -0800159 points = []
160 velocities = []
161 accelerations = []
162 s = []
163 thetas = []
164 omegas = []
165 curvatures = []
166
167 last_point = self.spline_eval_hermite(0)
168 distance = 0
169
170 # iterate through interim points and compute pos_vectors, and at predefined points arc length,
Ravago Jones5127ccc2022-07-31 16:32:45 -0700171 # velocity, and acceleration vectors and store them at their associated index
John Park1aaadc72018-11-04 13:13:08 -0800172 for i in range(0, self.resolution * resolution_multiplier):
173 t = i / (1.0 * self.resolution * resolution_multiplier)
174
175 current_point = self.spline_eval_hermite(t)
176 current_point_dt = self.spline_eval_hermite(t + dt)
177 current_s = np.linalg.norm(current_point - last_point)
178
179 ds = np.linalg.norm(current_point_dt - current_point)
180
181 distance = current_s + distance
182 # at important points compute important values and store
Ravago Jones5127ccc2022-07-31 16:32:45 -0700183 if i % resolution_multiplier == 0:
John Park1aaadc72018-11-04 13:13:08 -0800184 s.append(distance)
185 points.append(current_point)
186
187 v = self.spline_eval_hermite_v(t)
188 v_dt = self.spline_eval_hermite_v(t + dt)
189 theta_t = theta(v)
190 theta_dt = theta(v_dt)
191
192 a = (v_dt - v) / ds
193 omega = (theta_dt - theta_t) / ds
194 if np.linalg.norm(v) == 0:
195 curvature = 0
196 else:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700197 curvature = np.linalg.det(
198 np.column_stack((v, a)) / (np.linalg.norm(v)**(3 / 2)))
John Park1aaadc72018-11-04 13:13:08 -0800199
200 velocities.append(v)
201 accelerations.append(a)
202 thetas.append(theta_t)
203 omegas.append(omega)
204 if curvature == 0:
205 curvatures.append(0.0001)
206 else:
207 curvatures.append(curvature)
Ravago Jones5127ccc2022-07-31 16:32:45 -0700208
John Park1aaadc72018-11-04 13:13:08 -0800209 last_point = current_point
210
211 self.arc_lengths = np.array(s)
Ravago Jones5127ccc2022-07-31 16:32:45 -0700212 self.points = np.reshape(points, newshape=(-1, 2))
213 self.velocities = np.reshape(velocities, newshape=(-1, 2))
214 self.accelerations = np.reshape(accelerations, newshape=(-1, 2))
John Park1aaadc72018-11-04 13:13:08 -0800215 self.thetas = np.array(thetas)
216 self.omegas = np.array(omegas)
217 self.curvatures = np.array(curvatures)
218
John Park1aaadc72018-11-04 13:13:08 -0800219 def plot_diagnostics(self):
220 plt.figure("Spline")
221 plt.title('Spline')
222 plt.plot(self.points[:, 0], self.points[:, 1])
223 # plt.scatter(self.points[:, 0], self.points[:, 1])
224
225 plt.figure("Diagnostics")
226
Ravago Jones5127ccc2022-07-31 16:32:45 -0700227 plt.subplot(2, 2, 1)
John Park1aaadc72018-11-04 13:13:08 -0800228 plt.title('theta')
229 plt.xlabel('arc_length')
230 plt.ylabel('theta')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700231 theta, = plt.plot(self.arc_lengths, self.thetas, label='theta')
232 plt.legend(handles=[theta])
John Park1aaadc72018-11-04 13:13:08 -0800233
234 plt.subplot(2, 2, 2)
235 plt.title('omegas')
236 plt.xlabel('arc_length')
237 plt.ylabel('omega')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700238 omega, = plt.plot(self.arc_lengths, self.omegas, label='omega')
239 plt.legend(handles=[omega])
John Park1aaadc72018-11-04 13:13:08 -0800240
241 plt.subplot(2, 2, 3)
242 plt.title('Velocities')
243 plt.xlabel('arc_length')
244 plt.ylabel('velocity')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700245 dxds, = plt.plot(self.arc_lengths,
246 self.velocities[:, 0],
247 label='dx/ds')
248 dyds, = plt.plot(self.arc_lengths,
249 self.velocities[:, 1],
250 label='dy/ds')
251 plt.legend(handles=[dxds, dyds])
John Park1aaadc72018-11-04 13:13:08 -0800252
253 plt.subplot(2, 2, 4)
254 plt.title('Accelerations')
255 plt.xlabel('arc_length')
256 plt.ylabel('acceleration')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700257 dx2ds2, = plt.plot(self.arc_lengths,
258 self.accelerations[:, 0],
259 label='d^2x/ds^2')
260 dy2ds2, = plt.plot(self.arc_lengths,
261 self.accelerations[:, 1],
262 label='d^2x/ds^2')
263 plt.legend(handles=[dx2ds2, dy2ds2])
264
John Park1aaadc72018-11-04 13:13:08 -0800265
266# class defining a number of splines with convinience methods
267class Path:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700268
John Park1aaadc72018-11-04 13:13:08 -0800269 def __init__(self):
270 self.splines = []
271 self.knot_accels = []
272
273 def add_spline(self, spline):
274 self.splines.append(spline)
275
276 def get_K(self):
277 curvatures = []
278 for spline in self.splines:
279 curvatures.append(spline.curvatures)
280 return np.array(curvatures).flatten()
281
282 def get_S(self):
283 arc_lengths = []
284 for spline in self.splines:
285 arc_lengths.append(spline.arc_lengths)
286 return np.array(arc_lengths).flatten()
287
288 def get_points(self):
289 points = []
290 for spline in self.splines:
291 points.append(spline.points)
292 return points
293
294 def get_velocities(self, i):
295 velocities = []
296 for spline in self.splines:
297 velocities.append(spline.points)
298 return velocities
299
300 def remove_spine(self, i):
301 if i < len(self.splines):
302 self.splines.pop(i)
303 else:
304 print("index %f out of bounds, no spline of that index" % i)
305
Ravago Jones5127ccc2022-07-31 16:32:45 -0700306 def join(self, first_priority=False):
John Park1aaadc72018-11-04 13:13:08 -0800307 for i in range(0, len(self.splines)):
308 if first_priority & i != len(self.splines):
309 print("unfinished")
310
311
312# class which takes a Path object along with constraints and reparamterizes it with respect to time
313class Trajectory:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700314
315 def __init__(self,
316 path,
317 max_angular_accel=3,
318 max_voltage=11,
319 max_normal_accel=.2):
John Park1aaadc72018-11-04 13:13:08 -0800320 self.path = path
321 self.A = drivetrain.A_continuous
322 self.B = drivetrain.B_continuous
323 self.robot_radius = drivetrain.robot_radius
324 self.Kv = 100
325 self.robot_radius = 3
326 self.max_angular_accel = max_angular_accel
327 self.max_voltage = max_voltage
328 self.max_normal_accel = max_normal_accel
329
330 self.max_velocities_adhering_to_normal_accel = []
331 self.max_velocities_adhering_to_voltage = []
Ravago Jones5127ccc2022-07-31 16:32:45 -0700332 self.path.splines[0].setup(resolution_multiplier=100)
John Park1aaadc72018-11-04 13:13:08 -0800333
334 self.set_max_v_adhering_to_normal_accel()
335 self.max_voltageK_pass()
336
337 def set_max_v_adhering_to_normal_accel(self):
338 Ks = self.path.get_K()
Ravago Jones5127ccc2022-07-31 16:32:45 -0700339 accels = np.full_like(Ks, fill_value=self.max_normal_accel)
John Park1aaadc72018-11-04 13:13:08 -0800340 max_velocities = np.sqrt(np.abs(accels / Ks))
341 self.max_velocities_adhering_to_normal_accel = max_velocities
342
343 def max_voltageK_pass(self):
344 max_ds_dt = []
345 Ks = self.path.get_K()
Ravago Jones5127ccc2022-07-31 16:32:45 -0700346 turning_radii = np.full_like(Ks, fill_value=1) / np.abs(Ks)
John Park1aaadc72018-11-04 13:13:08 -0800347
Ravago Jones5127ccc2022-07-31 16:32:45 -0700348 # compute max steady-state velocity given voltage constraints
John Park1aaadc72018-11-04 13:13:08 -0800349 for i in range(0, len(Ks)):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700350 v_ratio = (turning_radii[i] + self.robot_radius) / (
351 turning_radii[i] - self.robot_radius)
352 matrix = np.array([[self.A[1, 1], self.A[1, 3], self.B[1, 1]],
353 [self.A[3, 1] - 1, self.A[3, 3], self.B[3, 1]],
354 [-1, v_ratio, 0]])
355 sols = np.array([
356 -1 * self.max_voltage * self.B[1, 0],
357 -1 * self.max_voltage * self.B[3, 0], 0
358 ])
John Park1aaadc72018-11-04 13:13:08 -0800359 Vs = np.dot(np.linalg.inv(matrix), sols)
360 max_ds_dt.append((Vs[0] + Vs[1]) / 2)
Ravago Jones5127ccc2022-07-31 16:32:45 -0700361
John Park1aaadc72018-11-04 13:13:08 -0800362 self.max_velocities_adhering_to_voltage = max_ds_dt
Ravago Jones5127ccc2022-07-31 16:32:45 -0700363
John Park1aaadc72018-11-04 13:13:08 -0800364 # compute the maximum acceleration we can ask for given voltage and, ya know, staying on the path.
John Park1aaadc72018-11-04 13:13:08 -0800365 '''
366 These methods use the continuous form of our drivetrain state equation
367 in order to compute the maximum acceleration which adheres to the path
368 and voltage constraints, as well as any arbitary set of constraints
369 on velocity as a function of arc_length
370 '''
Ravago Jones5127ccc2022-07-31 16:32:45 -0700371
John Park1aaadc72018-11-04 13:13:08 -0800372 def forward_accel_pass(self):
373 points = self.path.get_points()
374 velocities = self.path.get_velocities()
375 curvatures = self.path.get_K()
376 arc_lenghts = self.path.get_S()
377
378 for i in range(0, len(points)):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700379 #Xn1 =
380 pass
381
John Park1aaadc72018-11-04 13:13:08 -0800382 def backward_accelaration_pass(self):
383
Ravago Jones5127ccc2022-07-31 16:32:45 -0700384 print("max backward accel pass")
John Park1aaadc72018-11-04 13:13:08 -0800385
Ravago Jones5127ccc2022-07-31 16:32:45 -0700386 def plot_diagnostics(self, i=0):
John Park1aaadc72018-11-04 13:13:08 -0800387
388 plt.figure('max velocity')
389 plt.title('max_v_normal_accel')
390 plt.xlabel('arc_length')
391 plt.ylabel('max V')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700392 max_v_normal = plt.plot(self.path.get_S(),
393 self.max_velocities_adhering_to_normal_accel,
394 label='ds/dt (normal)') # , label = 'ds/dt')
395 curvature = plt.plot(self.path.get_S(),
396 1000 * np.abs(self.path.get_K()),
397 label='K')
398 max_v_K_V = plt.plot(self.path.get_S(),
399 self.max_velocities_adhering_to_voltage,
400 label='ds/dt (voltage)')
401 plt.legend(handles=[max_v_normal[0], curvature[0], max_v_K_V[0]])
402
John Park1aaadc72018-11-04 13:13:08 -0800403
404def main():
Ravago Jones5127ccc2022-07-31 16:32:45 -0700405 A = Hermite_Spline(np.array([0, 0]),
406 np.array([0, 400]),
407 np.array([200, 300]),
408 np.array([200, 200]),
409 resolution=200)
John Park1aaadc72018-11-04 13:13:08 -0800410 A.plot_diagnostics()
411 path = Path()
412 path.add_spline(A)
413 trajectory = Trajectory(path, 0)
414 trajectory.plot_diagnostics()
415 plt.show()
416
Ravago Jones5127ccc2022-07-31 16:32:45 -0700417
John Park1aaadc72018-11-04 13:13:08 -0800418main()