blob: 491286e49370d2ff03f37c5263c4ce3c0a0efa40 [file] [log] [blame]
Austin Schuh82162452022-02-07 22:01:45 -08001#!/usr/bin/python3
2
3from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
5import numpy
Austin Schuh2e28d872022-02-19 18:25:57 -08006import osqp
Austin Schuh82162452022-02-07 22:01:45 -08007import math
Austin Schuhc3e04282022-02-12 20:00:53 -08008import scipy.optimize
Austin Schuh82162452022-02-07 22:01:45 -08009import sys
10import math
11from y2022.control_loops.python import catapult_lib
12from matplotlib import pylab
13
14import gflags
15import glog
16
17FLAGS = gflags.FLAGS
18
Austin Schuhc3e04282022-02-12 20:00:53 -080019gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
Austin Schuh82162452022-02-07 22:01:45 -080020
21ball_mass = 0.25
22ball_diameter = 9.5 * 0.0254
23lever = 17.5 * 0.0254
24
25G = (14.0 / 72.0) * (12.0 / 33.0)
26
27
28def AddResistance(motor, resistance):
29 motor.resistance += resistance
30 return motor
31
Austin Schuhc3e04282022-02-12 20:00:53 -080032
Austin Schuh82162452022-02-07 22:01:45 -080033J_ball = 1.5 * ball_mass * lever * lever
34# Assuming carbon fiber, calculate the mass of the bar.
35M_bar = (1750 * lever * 0.0254 * 0.0254 * (1.0 - (1 - 0.07)**2.0))
36# And the moment of inertia.
37J_bar = 1.0 / 3.0 * M_bar * lever**2.0
38
39# Do the same for a theoretical cup. Assume a 40 thou thick carbon cup.
40M_cup = (1750 * 0.0254 * 0.04 * 2 * math.pi * (ball_diameter / 2.)**2.0)
41J_cup = M_cup * lever**2.0 + M_cup * (ball_diameter / 2.)**2.0
42
43print("J ball", ball_mass * lever * lever)
44print("J bar", J_bar)
45print("bar mass", M_bar)
46print("J cup", J_cup)
47print("cup mass", M_cup)
48
49J = (J_ball + J_bar + J_cup * 1.5)
50print("J", J)
51
Austin Schuhc3e04282022-02-12 20:00:53 -080052kCatapult = catapult_lib.CatapultParams(
Austin Schuh2e28d872022-02-19 18:25:57 -080053 name='Catapult',
Austin Schuh82162452022-02-07 22:01:45 -080054 motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.03),
55 G=G,
56 J=J,
57 lever=lever,
58 q_pos=0.01,
59 q_vel=10.0,
60 q_voltage=4.0,
61 r_pos=0.01,
62 controller_poles=[.93],
Austin Schuhc3e04282022-02-12 20:00:53 -080063 dt=0.00505)
64
Austin Schuhc3e04282022-02-12 20:00:53 -080065# Ideas for adjusting the cost function:
66#
67# Penalize battery current?
68# Penalize accel/rotor current?
69# Penalize velocity error off destination?
70# Penalize max u
71#
72# Ramp up U cost over time?
73# Once moving, open up saturation bounds
74#
75# We really want our cost function to be robust so that we can tolerate the
76# battery not delivering like we want at the end.
77
Austin Schuh2e28d872022-02-19 18:25:57 -080078def quadratic_cost(catapult, X_initial, X_final, horizon):
79 Q_final = numpy.matrix([[10000.0, 0.0], [0.0, 10000.0]])
Austin Schuhc3e04282022-02-12 20:00:53 -080080
Austin Schuh2e28d872022-02-19 18:25:57 -080081 As = numpy.vstack([catapult.A**(n + 1) for n in range(0, horizon)])
82 Af = catapult.A**horizon
83
84 Bs = numpy.matrix(numpy.zeros((2 * horizon, horizon)))
85 for n in range(0, horizon):
86 for m in range(0, n + 1):
87 Bs[n * 2:(n * 2) + 2, m] = catapult.A**(n - m) * catapult.B
88
89 Bf = Bs[horizon * 2 - 2:, :]
90
91 P_final = 2.0 * Bf.transpose() * Q_final * Bf
92 q_final = (2.0 * (Af * X_initial - X_final).transpose() * Q_final *
93 Bf).transpose()
94
95 constant_final = (Af * X_initial - X_final).transpose() * Q_final * (
96 Af * X_initial - X_final)
97
98 m = numpy.matrix([[catapult.A[1, 1] ** (n + 1)] for n in range(horizon)])
99 M = Bs[1:horizon * 2:2, :]
100
101 W = numpy.matrix(numpy.identity(horizon) - numpy.eye(horizon, horizon, -1)) / catapult.dt
102 w = -numpy.matrix(numpy.eye(horizon, 1, 0)) / catapult.dt
103
104
105 Pi = numpy.diag([
106 (0.01 ** 2.0) + (0.02 * max(0.0, 20 - (horizon - n)) / 20.0) ** 2.0 for n in range(horizon)
107 ])
108
109 P_accel = 2.0 * M.transpose() * W.transpose() * Pi * W * M
110 q_accel = 2.0 * (((W * m + w) * X_initial[1, 0]).transpose() * Pi * W * M).transpose()
111 constant_accel = ((W * m + w) * X_initial[1, 0]).transpose() * Pi * (
112 (W * m + w) * X_initial[1, 0])
113
114 return ((P_accel + P_final), (q_accel + q_final), (constant_accel + constant_final))
115
116
117def new_cost(catapult, X_initial, X_final, u):
118 u_matrix = numpy.matrix(u).transpose()
119 Q_final = numpy.matrix([[10000.0, 0.0], [0.0, 10000.0]])
120
121 As = numpy.vstack([catapult.A**(n + 1) for n in range(0, len(u))])
122 Af = catapult.A**len(u)
123
124 Bs = numpy.matrix(numpy.zeros((2 * len(u), len(u))))
125 for n in range(0, len(u)):
126 for m in range(0, n + 1):
127 Bs[n * 2:(n * 2) + 2, m] = catapult.A**(n - m) * catapult.B
128
129 Bf = Bs[len(u) * 2 - 2:, :]
130
131 P_final = 2.0 * Bf.transpose() * Q_final * Bf
132 q_final = (2.0 * (Af * X_initial - X_final).transpose() * Q_final *
133 Bf).transpose()
134
135 constant_final = (Af * X_initial - X_final).transpose() * Q_final * (
136 Af * X_initial - X_final)
137
138 m = numpy.matrix([[catapult.A[1, 1] ** (n + 1)] for n in range(len(u))])
139 M = Bs[1:len(u) * 2:2, :]
140
141 W = numpy.matrix(numpy.identity(len(u)) - numpy.eye(len(u), len(u), -1)) / catapult.dt
142 w = -numpy.matrix(numpy.eye(len(u), 1, 0)) * X_initial[1, 0] / catapult.dt
143
144 accel = W * (M * u_matrix + m * X_initial[1, 0]) + w
145
146 Pi = numpy.diag([
147 (0.01 ** 2.0) + (0.02 * max(0.0, 20 - (len(u) - n)) / 20.0) ** 2.0 for n in range(len(u))
148 ])
149
150 P_accel = 2.0 * M.transpose() * W.transpose() * Pi * W * M
151 q_accel = 2.0 * ((W * m * X_initial[1, 0] + w).transpose() * Pi * W * M).transpose()
152 constant_accel = (W * m * X_initial[1, 0] +
153 w).transpose() * Pi * (W * m * X_initial[1, 0] + w)
154
155
156def mpc_cost(catapult, X_initial, X_final, u_matrix):
157
Austin Schuhc3e04282022-02-12 20:00:53 -0800158 X = X_initial.copy()
159 cost = 0.0
160 last_u = u_matrix[0]
161 max_u = 0.0
162 for count, u in enumerate(u_matrix):
163 v_prior = X[1, 0]
164 X = catapult.A * X + catapult.B * numpy.matrix([[u]])
165 v = X[1, 0]
166
167 # Smoothness cost on voltage change and voltage.
168 #cost += (u - last_u) ** 2.0
169 #cost += (u - 6.0) ** 2.0
170
171 measured_a = (v - v_prior) / catapult.dt
172 expected_a = 0.0
173
174 # Our good cost!
175 cost_scalar = 0.02 * max(0.0, (20 - (len(u_matrix) - count)) / 20.)
176 cost += ((measured_a - expected_a) * cost_scalar)**2.0
177 cost += (measured_a * 0.010)**2.0
178
179 # Quadratic cost. This delays as long as possible, but approximates a
180 # LQR until saturation.
181 #cost += (u - 0.0) ** 2.0
182 #cost += (0.1 * (X_final[0, 0] - X[0, 0])) ** 2.0
183 #cost += (0.5 * (X_final[1, 0] - X[1, 0])) ** 2.0
184
185 max_u = max(u, max_u)
186 last_u = u
187
188 # Penalize max power usage. This is hard to solve.
189 #cost += max_u * 10
190
191 terminal_cost = (X - X_final).transpose() * numpy.matrix(
192 [[10000.0, 0.0], [0.0, 10000.0]]) * (X - X_final)
193 cost += terminal_cost[0, 0]
194
195 return cost
196
197
Austin Schuh2e28d872022-02-19 18:25:57 -0800198def SolveCatapult(catapult, X_initial, X_final, u):
Austin Schuhc3e04282022-02-12 20:00:53 -0800199 """ Solves for the optimal action given a seed, state, and target """
200 def vbat_constraint(z, i):
201 return 12.0 - z[i]
202
203 def forward(z, i):
204 return z[i]
205
Austin Schuh2e28d872022-02-19 18:25:57 -0800206 P, q, c = quadratic_cost(catapult, X_initial, X_final, len(u))
Austin Schuhc3e04282022-02-12 20:00:53 -0800207
Austin Schuh2e28d872022-02-19 18:25:57 -0800208
209 def mpc_cost2(u_solver):
210 u_matrix = numpy.matrix(u_solver).transpose()
211 cost = mpc_cost(catapult, X_initial, X_final, u_solver)
212 return cost
213
214
215 def mpc_cost3(u_solver):
216 u_matrix = numpy.matrix(u_solver).transpose()
217 return (0.5 * u_matrix.transpose() * P * u_matrix +
218 q.transpose() * u_matrix + c)[0, 0]
219
220 # If we provide scipy with the analytical jacobian and hessian, it solves
221 # more accurately and a *lot* faster.
222 def jacobian(u):
223 u_matrix = numpy.matrix(u).transpose()
224 return numpy.array(P * u_matrix + q)
225
226 def hessian(u):
227 return numpy.array(P)
228
229 constraints = []
230 constraints += [{
Austin Schuhc3e04282022-02-12 20:00:53 -0800231 'type': 'ineq',
232 'fun': vbat_constraint,
233 'args': (i, )
234 } for i in numpy.arange(len(u))]
235
236 constraints += [{
237 'type': 'ineq',
238 'fun': forward,
239 'args': (i, )
240 } for i in numpy.arange(len(u))]
241
Austin Schuh2e28d872022-02-19 18:25:57 -0800242 result = scipy.optimize.minimize(mpc_cost3,
Austin Schuhc3e04282022-02-12 20:00:53 -0800243 u,
Austin Schuh2e28d872022-02-19 18:25:57 -0800244 jac=jacobian,
245 hess=hessian,
Austin Schuhc3e04282022-02-12 20:00:53 -0800246 method='SLSQP',
Austin Schuh2e28d872022-02-19 18:25:57 -0800247 tol=1e-12,
Austin Schuhc3e04282022-02-12 20:00:53 -0800248 constraints=constraints)
249 print(result)
250
251 return result.x
252
253
254def CatapultProblem():
255 c = catapult_lib.Catapult(kCatapult)
256
257 kHorizon = 40
258
259 u = [0.0] * kHorizon
260 X_initial = numpy.matrix([[0.0], [0.0]])
261 X_final = numpy.matrix([[2.0], [25.0]])
262
263
264 X_initial = numpy.matrix([[0.0], [0.0]])
265 X = X_initial.copy()
266
267 t_samples = [0.0]
268 x_samples = [0.0]
269 v_samples = [0.0]
270 a_samples = [0.0]
271
272 # Iteratively solve our MPC and simulate it.
273 u_samples = []
274 for i in range(kHorizon):
Austin Schuh2e28d872022-02-19 18:25:57 -0800275 u_horizon = SolveCatapult(c, X, X_final, u)
276
Austin Schuhc3e04282022-02-12 20:00:53 -0800277 u_samples.append(u_horizon[0])
278 v_prior = X[1, 0]
279 X = c.A * X + c.B * numpy.matrix([[u_horizon[0]]])
280 v = X[1, 0]
281 t_samples.append(t_samples[-1] + c.dt)
282 x_samples.append(X[0, 0])
283 v_samples.append(X[1, 0])
284 a_samples.append((v - v_prior) / c.dt)
285
286 u = u_horizon[1:]
287
288 print('Final state', X.transpose())
289 print('Final velocity', X[1, 0] * lever)
290 pylab.subplot(2, 1, 1)
291 pylab.plot(t_samples, x_samples, label="x")
292 pylab.plot(t_samples, v_samples, label="v")
293 pylab.plot(t_samples[1:], u_samples, label="u")
294 pylab.legend()
295 pylab.subplot(2, 1, 2)
296 pylab.plot(t_samples, a_samples, label="a")
297 pylab.legend()
298
299 pylab.show()
Austin Schuh82162452022-02-07 22:01:45 -0800300
301
302def main(argv):
303 # Do all our math with a lower voltage so we have headroom.
304 U = numpy.matrix([[9.0]])
Austin Schuhc3e04282022-02-12 20:00:53 -0800305
Austin Schuh2e28d872022-02-19 18:25:57 -0800306 prob = osqp.OSQP()
307
308 kHorizon = 40
309 catapult = catapult_lib.Catapult(kCatapult)
310 X_initial = numpy.matrix([[0.0], [0.0]])
311 X_final = numpy.matrix([[2.0], [25.0]])
312 P, q, c = quadratic_cost(catapult, X_initial, X_final, kHorizon)
313 A = numpy.identity(kHorizon)
314 l = numpy.zeros((kHorizon, 1))
315 u = numpy.ones((kHorizon, 1)) * 12.0
316
317 prob.setup(scipy.sparse.csr_matrix(P),
318 q,
319 scipy.sparse.csr_matrix(A),
320 l,
321 u,
322 warm_start=True)
323
324 result = prob.solve()
325 # Check solver status
326 if result.info.status != 'solved':
327 raise ValueError('OSQP did not solve the problem!')
328
329 # Apply first control input to the plant
330 print(result.x)
Austin Schuh82162452022-02-07 22:01:45 -0800331
332 if FLAGS.plot:
Austin Schuh2e28d872022-02-19 18:25:57 -0800333 print(
334 "For G:", G, " max speed ",
335 catapult_lib.MaxSpeed(params=kCatapult,
336 U=U,
337 final_position=math.pi / 2.0))
338
339 CatapultProblem()
340 return 0
341
Austin Schuhc3e04282022-02-12 20:00:53 -0800342 catapult_lib.PlotShot(kCatapult, U, final_position=math.pi / 4.0)
Austin Schuh82162452022-02-07 22:01:45 -0800343
344 gs = []
345 speed = []
346 for i in numpy.linspace(0.01, 0.15, 150):
Austin Schuhc3e04282022-02-12 20:00:53 -0800347 kCatapult.G = i
348 gs.append(kCatapult.G)
349 speed.append(
350 catapult_lib.MaxSpeed(params=kCatapult,
351 U=U,
352 final_position=math.pi / 2.0))
353 pylab.plot(gs, speed, label="max_speed")
Austin Schuh82162452022-02-07 22:01:45 -0800354 pylab.show()
Austin Schuh2e28d872022-02-19 18:25:57 -0800355
356 if len(argv) != 5:
357 glog.fatal(
358 'Expected .h file name and .cc file name for the catapult and integral catapult.'
359 )
360 else:
361 namespaces = ['y2022', 'control_loops', 'superstructure', 'catapult']
362 catapult_lib.WriteCatapult(kCatapult, argv[1:3], argv[3:5], namespaces)
363 return 0
Austin Schuh82162452022-02-07 22:01:45 -0800364
365
366if __name__ == '__main__':
367 argv = FLAGS(sys.argv)
368 sys.exit(main(argv))