blob: fcb3129d5bc641c2750252a7afb97ebee361aa94 [file] [log] [blame]
Austin Schuh404e17f2018-01-24 19:32:10 -08001#!/usr/bin/python3
2
3import numpy
4import time
5import scipy.optimize
6from matplotlib import pylab
7from frc971.control_loops.python import controls
8
9dt = 0.05
10
11def 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
20def 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
36def discrete_dynamics(X, U):
37 return RungeKutta(lambda startingX: dynamics(startingX, U), X, dt)
38
39
40def 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
51def 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
62def 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
88def 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
113class 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
181if __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()