John Park | 1aaadc7 | 2018-11-04 13:13:08 -0800 | [diff] [blame^] | 1 | #!/usr/bin/python
|
| 2 | import numpy as np
|
| 3 | import matplotlib.pyplot as plt
|
| 4 | from 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 |
|
| 9 | kDrivetrain = 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 |
|
| 27 | drivetrain = drivetrain.Drivetrain(kDrivetrain)
|
| 28 | # set up coefficients for Hemrite basis function evaluation
|
| 29 | coeffs = 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]])
|
| 30 | coeffs_prime = np.empty_like(coeffs)
|
| 31 | for 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 |
|
| 35 | def 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 |
|
| 44 | def normalize(v):
|
| 45 | norm = np.linalg.norm(v)
|
| 46 | return v / norm
|
| 47 |
|
| 48 | def theta(v):
|
| 49 | return np.arctan2(v[1], v[0])
|
| 50 |
|
| 51 | # evaluate Nth hermite basis function at t
|
| 52 | def 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 |
|
| 55 | def 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
|
| 59 | class 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
|
| 235 | class 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
|
| 280 | class 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 |
|
| 357 | def 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 |
|
| 366 | main()
|