Austin Schuh | 404e17f | 2018-01-24 19:32:10 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | import numpy |
| 4 | import time |
| 5 | import scipy.optimize |
| 6 | from matplotlib import pylab |
| 7 | from frc971.control_loops.python import controls |
| 8 | |
| 9 | dt = 0.05 |
| 10 | |
| 11 | def RungeKutta(f, x, dt): |
| 12 | """4th order RungeKutta integration of F starting at X.""" |
| 13 | a = f(x) |
| 14 | b = f(x + dt / 2.0 * a) |
| 15 | c = f(x + dt / 2.0 * b) |
| 16 | d = f(x + dt * c) |
| 17 | return x + dt * (a + 2.0 * b + 2.0 * c + d) / 6.0 |
| 18 | |
| 19 | |
| 20 | def dynamics(X, U): |
| 21 | """Calculates the dynamics for a double jointed arm. |
| 22 | |
| 23 | Args: |
| 24 | X, numpy.matrix(4, 1), The state. [theta1, omega1, theta2, omega2] |
| 25 | U, numpy.matrix(2, 1), The input. [torque1, torque2] |
| 26 | |
| 27 | Returns: |
| 28 | numpy.matrix(4, 1), The derivative of the dynamics. |
| 29 | """ |
| 30 | return numpy.matrix([[X[1, 0]], |
| 31 | [U[0, 0]], |
| 32 | [X[3, 0]], |
| 33 | [U[1, 0]]]) |
| 34 | |
| 35 | |
| 36 | def discrete_dynamics(X, U): |
| 37 | return RungeKutta(lambda startingX: dynamics(startingX, U), X, dt) |
| 38 | |
| 39 | |
| 40 | def U_from_array(U_array): |
| 41 | """Converts the U array from the optimizer to a bunch of column vectors. |
| 42 | |
| 43 | Args: |
| 44 | U_array, numpy.array[N] The U coordinates in v, av, v, av, ... |
| 45 | |
| 46 | Returns: |
| 47 | numpy.matrix[2, N/2] with [[v, v, v, ...], [av, av, av, ...]] |
| 48 | """ |
| 49 | return numpy.matrix(U_array).reshape((2, -1), order='F') |
| 50 | |
| 51 | def U_to_array(U_matrix): |
| 52 | """Converts the U matrix to the U array for the optimizer. |
| 53 | |
| 54 | Args: |
| 55 | U_matrix, numpy.matrix[2, N/2] with [[v, v, v, ...], [av, av, av, ...]] |
| 56 | |
| 57 | Returns: |
| 58 | numpy.array[N] The U coordinates in v, av, v, av, ... |
| 59 | """ |
| 60 | return numpy.array(U_matrix.reshape((1, -1), order='F')) |
| 61 | |
| 62 | def numerical_jacobian_x(fn, X, U, epsilon=1e-4): |
| 63 | """Numerically estimates the jacobian around X, U in X. |
| 64 | |
| 65 | Args: |
| 66 | fn: A function of X, U. |
| 67 | X: numpy.matrix(num_states, 1), The state vector to take the jacobian |
| 68 | around. |
| 69 | U: numpy.matrix(num_inputs, 1), The input vector to take the jacobian |
| 70 | around. |
| 71 | |
| 72 | Returns: |
| 73 | numpy.matrix(num_states, num_states), The jacobian of fn with X as the |
| 74 | variable. |
| 75 | """ |
| 76 | num_states = X.shape[0] |
| 77 | nominal = fn(X, U) |
| 78 | answer = numpy.matrix(numpy.zeros((nominal.shape[0], num_states))) |
| 79 | # It's more expensive, but +- epsilon will be more reliable |
| 80 | for i in range(0, num_states): |
| 81 | dX_plus = X.copy() |
| 82 | dX_plus[i] += epsilon |
| 83 | dX_minus = X.copy() |
| 84 | dX_minus[i] -= epsilon |
| 85 | answer[:, i] = (fn(dX_plus, U) - fn(dX_minus, U)) / epsilon / 2.0 |
| 86 | return answer |
| 87 | |
| 88 | def numerical_jacobian_u(fn, X, U, epsilon=1e-4): |
| 89 | """Numerically estimates the jacobian around X, U in U. |
| 90 | |
| 91 | Args: |
| 92 | fn: A function of X, U. |
| 93 | X: numpy.matrix(num_states, 1), The state vector to take the jacobian |
| 94 | around. |
| 95 | U: numpy.matrix(num_inputs, 1), The input vector to take the jacobian |
| 96 | around. |
| 97 | |
| 98 | Returns: |
| 99 | numpy.matrix(num_states, num_inputs), The jacobian of fn with U as the |
| 100 | variable. |
| 101 | """ |
| 102 | num_inputs = U.shape[0] |
| 103 | nominal = fn(X, U) |
| 104 | answer = numpy.matrix(numpy.zeros((nominal.shape[0], num_inputs))) |
| 105 | for i in range(0, num_inputs): |
| 106 | dU_plus = U.copy() |
| 107 | dU_plus[i] += epsilon |
| 108 | dU_minus = U.copy() |
| 109 | dU_minus[i] -= epsilon |
| 110 | answer[:, i] = (fn(X, dU_plus) - fn(X, dU_minus)) / epsilon / 2.0 |
| 111 | return answer |
| 112 | |
| 113 | class Cost(object): |
| 114 | def __init__(self): |
| 115 | q_pos = 0.5 |
| 116 | q_vel = 1.65 |
| 117 | self.Q = numpy.matrix(numpy.diag([ |
| 118 | 1.0 / (q_pos ** 2.0), 1.0 / (q_vel ** 2.0), |
| 119 | 1.0 / (q_pos ** 2.0), 1.0 / (q_vel ** 2.0)])) |
| 120 | |
| 121 | self.R = numpy.matrix(numpy.diag([1.0 / (12.0 ** 2.0), |
| 122 | 1.0 / (12.0 ** 2.0)])) |
| 123 | |
| 124 | final_A = numerical_jacobian_x(discrete_dynamics, |
| 125 | numpy.matrix(numpy.zeros((4, 1))), |
| 126 | numpy.matrix(numpy.zeros((2, 1)))) |
| 127 | final_B = numerical_jacobian_u(discrete_dynamics, |
| 128 | numpy.matrix(numpy.zeros((4, 1))), |
| 129 | numpy.matrix(numpy.zeros((2, 1)))) |
| 130 | #print 'Final A', final_A |
| 131 | #print 'Final B', final_B |
| 132 | K, self.S = controls.dlqr( |
| 133 | final_A, final_B, self.Q, self.R, optimal_cost_function=True) |
| 134 | print K |
| 135 | print self.S |
| 136 | |
| 137 | def cost(self, U_array, X): |
| 138 | """Computes the cost given the inital position and the U array. |
| 139 | |
| 140 | Args: |
| 141 | U_array: numpy.array[N] The U coordinates. |
| 142 | X: numpy.matrix[3, 1] The cartesian coordinates of the starting |
| 143 | location. |
| 144 | |
| 145 | Returns: |
| 146 | double, The quadratic cost of evaluating U. |
| 147 | """ |
| 148 | |
| 149 | X_mod = X.copy() |
| 150 | U_matrix = U_from_array(U_array) |
| 151 | my_cost = 0 |
| 152 | |
| 153 | for U in U_matrix.T: |
| 154 | # Handle a keep out zone. |
| 155 | penalized_cost = 0.0 |
| 156 | if X_mod[0, 0] > 0.5 and X_mod[2, 0] < 0.8: |
| 157 | out_of_bound1 = 0.8 - X_mod[2, 0] |
| 158 | penalized_cost += 1000.0 * (out_of_bound1 ** 2.0 + 0.1 * out_of_bound1) |
| 159 | |
| 160 | out_of_bound2 = X_mod[0, 0] - 0.5 |
| 161 | penalized_cost += 1000.0 * (out_of_bound2 ** 2.0 + 0.1 * out_of_bound2) |
| 162 | |
| 163 | U = U.T |
| 164 | my_cost += U.T * self.R * U + X_mod.T * self.Q * X_mod + penalized_cost |
| 165 | X_mod = discrete_dynamics(X_mod, U) |
| 166 | |
| 167 | return my_cost + 0.5 * X_mod.T * self.S * X_mod |
| 168 | |
| 169 | |
| 170 | # TODO(austin): Add Parker's constraints in. |
| 171 | # TODO(austin): Real dynamics from dad. |
| 172 | # TODO(austin): Look at grid resolution needed. Grid a section in open space |
| 173 | # and look at curvature of the grid. Try motions using the grid. |
| 174 | # |
| 175 | # https://docs.scipy.org/doc/scipy-0.16.1/reference/generated/scipy.interpolate.RegularGridInterpolator.html |
| 176 | # Look at a C++ version so we can build a large space. |
| 177 | |
| 178 | # TODO(austin): Larger timesteps further out? |
| 179 | |
| 180 | |
| 181 | if __name__ == '__main__': |
| 182 | X = numpy.matrix([[1.0], |
| 183 | [0.0], |
| 184 | [1.0], |
| 185 | [0.0]]) |
| 186 | theta1_array = [] |
| 187 | omega1_array = [] |
| 188 | theta2_array = [] |
| 189 | omega2_array = [] |
| 190 | |
| 191 | cost_array = [] |
| 192 | |
| 193 | time_array = [] |
| 194 | u0_array = [] |
| 195 | u1_array = [] |
| 196 | |
| 197 | num_steps = 40 |
| 198 | |
| 199 | cost_obj = Cost() |
| 200 | |
| 201 | U_array = numpy.zeros((num_steps * 2)) |
| 202 | for i in range(400): |
| 203 | print('Iteration', i) |
| 204 | start_time = time.time() |
| 205 | # Solve the NMPC |
| 206 | U_array, fx, _, _, _ = scipy.optimize.fmin_slsqp( |
| 207 | cost_obj.cost, U_array.copy(), bounds = [(-12, 12), (-12, 12)] * num_steps, |
| 208 | args=(X,), iter=500, full_output=True) |
| 209 | U_matrix = U_from_array(U_array) |
| 210 | |
| 211 | # Save variables for plotting. |
| 212 | cost_array.append(fx[0, 0]) |
| 213 | u0_array.append(U_matrix[0, 0]) |
| 214 | u1_array.append(U_matrix[1, 0]) |
| 215 | |
| 216 | theta1_array.append(X[0, 0]) |
| 217 | omega1_array.append(X[1, 0]) |
| 218 | theta2_array.append(X[2, 0]) |
| 219 | omega2_array.append(X[3, 0]) |
| 220 | |
| 221 | time_array.append(i * dt) |
| 222 | |
| 223 | # Simulate the dynamics |
| 224 | X = discrete_dynamics(X, U_matrix[:, 0]) |
| 225 | |
| 226 | U_array = U_to_array(numpy.hstack((U_matrix[:, 1:], numpy.matrix([[0], [0]])))) |
| 227 | print 'Took %f to evaluate' % (time.time() - start_time) |
| 228 | |
| 229 | if fx < 1e-05: |
| 230 | print('Cost is', fx, 'after', i, 'cycles, finishing early') |
| 231 | break |
| 232 | |
| 233 | # Plot |
| 234 | pylab.figure('trajectory') |
| 235 | pylab.plot(theta1_array, theta2_array, label = 'trajectory') |
| 236 | |
| 237 | fig, ax1 = fig, ax1 = pylab.subplots() |
| 238 | fig.suptitle('time') |
| 239 | ax1.plot(time_array, theta1_array, label='theta1') |
| 240 | ax1.plot(time_array, omega1_array, label='omega1') |
| 241 | ax1.plot(time_array, theta2_array, label = 'theta2') |
| 242 | ax1.plot(time_array, omega2_array, label='omega2') |
| 243 | ax2 = ax1.twinx() |
| 244 | ax2.plot(time_array, cost_array, 'k', label='cost') |
| 245 | ax1.legend(loc=2) |
| 246 | ax2.legend() |
| 247 | |
| 248 | pylab.figure('u') |
| 249 | pylab.plot(time_array, u0_array, label='u0') |
| 250 | pylab.plot(time_array, u1_array, label='u1') |
| 251 | pylab.legend() |
| 252 | |
| 253 | pylab.show() |