blob: e98447864c0ed7c70b9da43ef3e60d9bd860cecf [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
Austin Schuhb5af5912022-03-27 13:31:20 -070043J = (0.0 * J_ball + J_bar + J_cup * 0.0)
milind-u37dc40b2022-03-08 19:51:27 -080044JEmpty = (J_bar + J_cup * 0.0)
Austin Schuh82162452022-02-07 22:01:45 -080045
Austin Schuhac61c882022-02-23 17:22:22 -080046kCatapultWithBall = catapult_lib.CatapultParams(
Austin Schuh2e28d872022-02-19 18:25:57 -080047 name='Catapult',
Austin Schuhdfeb83c2022-03-12 13:17:49 -080048 motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.01),
Austin Schuh82162452022-02-07 22:01:45 -080049 G=G,
50 J=J,
Austin Schuhac61c882022-02-23 17:22:22 -080051 radius=lever,
52 q_pos=2.8,
53 q_vel=20.0,
milind-u37dc40b2022-03-08 19:51:27 -080054 kalman_q_pos=0.01,
Austin Schuhac61c882022-02-23 17:22:22 -080055 kalman_q_vel=1.0,
56 kalman_q_voltage=1.5,
milind-u37dc40b2022-03-08 19:51:27 -080057 kalman_r_position=0.001)
Austin Schuhac61c882022-02-23 17:22:22 -080058
59kCatapultEmpty = catapult_lib.CatapultParams(
60 name='Catapult',
milind-u37dc40b2022-03-08 19:51:27 -080061 motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.02),
Austin Schuhac61c882022-02-23 17:22:22 -080062 G=G,
63 J=JEmpty,
64 radius=lever,
65 q_pos=2.8,
66 q_vel=20.0,
67 kalman_q_pos=0.12,
68 kalman_q_vel=1.0,
69 kalman_q_voltage=1.5,
70 kalman_r_position=0.05)
Austin Schuhc3e04282022-02-12 20:00:53 -080071
Austin Schuhc3e04282022-02-12 20:00:53 -080072# Ideas for adjusting the cost function:
73#
74# Penalize battery current?
75# Penalize accel/rotor current?
76# Penalize velocity error off destination?
77# Penalize max u
78#
79# Ramp up U cost over time?
80# Once moving, open up saturation bounds
81#
82# We really want our cost function to be robust so that we can tolerate the
83# battery not delivering like we want at the end.
84
Ravago Jones5127ccc2022-07-31 16:32:45 -070085
Austin Schuh2e28d872022-02-19 18:25:57 -080086def quadratic_cost(catapult, X_initial, X_final, horizon):
87 Q_final = numpy.matrix([[10000.0, 0.0], [0.0, 10000.0]])
Austin Schuhc3e04282022-02-12 20:00:53 -080088
Austin Schuh2e28d872022-02-19 18:25:57 -080089 As = numpy.vstack([catapult.A**(n + 1) for n in range(0, horizon)])
90 Af = catapult.A**horizon
91
92 Bs = numpy.matrix(numpy.zeros((2 * horizon, horizon)))
93 for n in range(0, horizon):
94 for m in range(0, n + 1):
95 Bs[n * 2:(n * 2) + 2, m] = catapult.A**(n - m) * catapult.B
96
97 Bf = Bs[horizon * 2 - 2:, :]
98
99 P_final = 2.0 * Bf.transpose() * Q_final * Bf
100 q_final = (2.0 * (Af * X_initial - X_final).transpose() * Q_final *
Ravago Jones5127ccc2022-07-31 16:32:45 -0700101 Bf).transpose()
Austin Schuh2e28d872022-02-19 18:25:57 -0800102
103 constant_final = (Af * X_initial - X_final).transpose() * Q_final * (
104 Af * X_initial - X_final)
105
Ravago Jones5127ccc2022-07-31 16:32:45 -0700106 m = numpy.matrix([[catapult.A[1, 1]**(n + 1)] for n in range(horizon)])
Austin Schuh2e28d872022-02-19 18:25:57 -0800107 M = Bs[1:horizon * 2:2, :]
108
Ravago Jones5127ccc2022-07-31 16:32:45 -0700109 W = numpy.matrix(
110 numpy.identity(horizon) -
111 numpy.eye(horizon, horizon, -1)) / catapult.dt
Austin Schuh2e28d872022-02-19 18:25:57 -0800112 w = -numpy.matrix(numpy.eye(horizon, 1, 0)) / catapult.dt
113
Austin Schuh2e28d872022-02-19 18:25:57 -0800114 Pi = numpy.diag([
Ravago Jones5127ccc2022-07-31 16:32:45 -0700115 (0.01**2.0) + (0.02 * max(0.0, 20 - (horizon - n)) / 20.0)**2.0
116 for n in range(horizon)
Austin Schuh2e28d872022-02-19 18:25:57 -0800117 ])
118
119 P_accel = 2.0 * M.transpose() * W.transpose() * Pi * W * M
Ravago Jones5127ccc2022-07-31 16:32:45 -0700120 q_accel = 2.0 * ((
121 (W * m + w) * X_initial[1, 0]).transpose() * Pi * W * M).transpose()
Austin Schuh2e28d872022-02-19 18:25:57 -0800122 constant_accel = ((W * m + w) * X_initial[1, 0]).transpose() * Pi * (
123 (W * m + w) * X_initial[1, 0])
124
Ravago Jones5127ccc2022-07-31 16:32:45 -0700125 return ((P_accel + P_final), (q_accel + q_final),
126 (constant_accel + constant_final))
Austin Schuh2e28d872022-02-19 18:25:57 -0800127
128
129def new_cost(catapult, X_initial, X_final, u):
130 u_matrix = numpy.matrix(u).transpose()
131 Q_final = numpy.matrix([[10000.0, 0.0], [0.0, 10000.0]])
132
133 As = numpy.vstack([catapult.A**(n + 1) for n in range(0, len(u))])
134 Af = catapult.A**len(u)
135
136 Bs = numpy.matrix(numpy.zeros((2 * len(u), len(u))))
137 for n in range(0, len(u)):
138 for m in range(0, n + 1):
139 Bs[n * 2:(n * 2) + 2, m] = catapult.A**(n - m) * catapult.B
140
141 Bf = Bs[len(u) * 2 - 2:, :]
142
143 P_final = 2.0 * Bf.transpose() * Q_final * Bf
144 q_final = (2.0 * (Af * X_initial - X_final).transpose() * Q_final *
Ravago Jones5127ccc2022-07-31 16:32:45 -0700145 Bf).transpose()
Austin Schuh2e28d872022-02-19 18:25:57 -0800146
147 constant_final = (Af * X_initial - X_final).transpose() * Q_final * (
148 Af * X_initial - X_final)
149
Ravago Jones5127ccc2022-07-31 16:32:45 -0700150 m = numpy.matrix([[catapult.A[1, 1]**(n + 1)] for n in range(len(u))])
Austin Schuh2e28d872022-02-19 18:25:57 -0800151 M = Bs[1:len(u) * 2:2, :]
152
Ravago Jones5127ccc2022-07-31 16:32:45 -0700153 W = numpy.matrix(numpy.identity(len(u)) -
154 numpy.eye(len(u), len(u), -1)) / catapult.dt
Austin Schuh2e28d872022-02-19 18:25:57 -0800155 w = -numpy.matrix(numpy.eye(len(u), 1, 0)) * X_initial[1, 0] / catapult.dt
156
157 accel = W * (M * u_matrix + m * X_initial[1, 0]) + w
158
159 Pi = numpy.diag([
Ravago Jones5127ccc2022-07-31 16:32:45 -0700160 (0.01**2.0) + (0.02 * max(0.0, 20 - (len(u) - n)) / 20.0)**2.0
161 for n in range(len(u))
Austin Schuh2e28d872022-02-19 18:25:57 -0800162 ])
163
164 P_accel = 2.0 * M.transpose() * W.transpose() * Pi * W * M
Ravago Jones5127ccc2022-07-31 16:32:45 -0700165 q_accel = 2.0 * (
166 (W * m * X_initial[1, 0] + w).transpose() * Pi * W * M).transpose()
Austin Schuh2e28d872022-02-19 18:25:57 -0800167 constant_accel = (W * m * X_initial[1, 0] +
168 w).transpose() * Pi * (W * m * X_initial[1, 0] + w)
169
170
171def mpc_cost(catapult, X_initial, X_final, u_matrix):
172
Austin Schuhc3e04282022-02-12 20:00:53 -0800173 X = X_initial.copy()
174 cost = 0.0
175 last_u = u_matrix[0]
176 max_u = 0.0
177 for count, u in enumerate(u_matrix):
178 v_prior = X[1, 0]
179 X = catapult.A * X + catapult.B * numpy.matrix([[u]])
180 v = X[1, 0]
181
182 # Smoothness cost on voltage change and voltage.
183 #cost += (u - last_u) ** 2.0
184 #cost += (u - 6.0) ** 2.0
185
186 measured_a = (v - v_prior) / catapult.dt
187 expected_a = 0.0
188
189 # Our good cost!
190 cost_scalar = 0.02 * max(0.0, (20 - (len(u_matrix) - count)) / 20.)
191 cost += ((measured_a - expected_a) * cost_scalar)**2.0
192 cost += (measured_a * 0.010)**2.0
193
194 # Quadratic cost. This delays as long as possible, but approximates a
195 # LQR until saturation.
196 #cost += (u - 0.0) ** 2.0
197 #cost += (0.1 * (X_final[0, 0] - X[0, 0])) ** 2.0
198 #cost += (0.5 * (X_final[1, 0] - X[1, 0])) ** 2.0
199
200 max_u = max(u, max_u)
201 last_u = u
202
203 # Penalize max power usage. This is hard to solve.
204 #cost += max_u * 10
205
206 terminal_cost = (X - X_final).transpose() * numpy.matrix(
207 [[10000.0, 0.0], [0.0, 10000.0]]) * (X - X_final)
208 cost += terminal_cost[0, 0]
209
210 return cost
211
212
Austin Schuh2e28d872022-02-19 18:25:57 -0800213def SolveCatapult(catapult, X_initial, X_final, u):
Austin Schuhc3e04282022-02-12 20:00:53 -0800214 """ Solves for the optimal action given a seed, state, and target """
Ravago Jones5127ccc2022-07-31 16:32:45 -0700215
Austin Schuhc3e04282022-02-12 20:00:53 -0800216 def vbat_constraint(z, i):
217 return 12.0 - z[i]
218
219 def forward(z, i):
220 return z[i]
221
Austin Schuh2e28d872022-02-19 18:25:57 -0800222 P, q, c = quadratic_cost(catapult, X_initial, X_final, len(u))
Austin Schuhc3e04282022-02-12 20:00:53 -0800223
Austin Schuh2e28d872022-02-19 18:25:57 -0800224 def mpc_cost2(u_solver):
225 u_matrix = numpy.matrix(u_solver).transpose()
226 cost = mpc_cost(catapult, X_initial, X_final, u_solver)
227 return cost
228
Austin Schuh2e28d872022-02-19 18:25:57 -0800229 def mpc_cost3(u_solver):
230 u_matrix = numpy.matrix(u_solver).transpose()
231 return (0.5 * u_matrix.transpose() * P * u_matrix +
232 q.transpose() * u_matrix + c)[0, 0]
233
234 # If we provide scipy with the analytical jacobian and hessian, it solves
235 # more accurately and a *lot* faster.
236 def jacobian(u):
237 u_matrix = numpy.matrix(u).transpose()
238 return numpy.array(P * u_matrix + q)
239
240 def hessian(u):
241 return numpy.array(P)
242
243 constraints = []
244 constraints += [{
Austin Schuhc3e04282022-02-12 20:00:53 -0800245 'type': 'ineq',
246 'fun': vbat_constraint,
247 'args': (i, )
248 } for i in numpy.arange(len(u))]
249
250 constraints += [{
251 'type': 'ineq',
252 'fun': forward,
253 'args': (i, )
254 } for i in numpy.arange(len(u))]
255
Austin Schuh2e28d872022-02-19 18:25:57 -0800256 result = scipy.optimize.minimize(mpc_cost3,
Austin Schuhc3e04282022-02-12 20:00:53 -0800257 u,
Austin Schuh2e28d872022-02-19 18:25:57 -0800258 jac=jacobian,
259 hess=hessian,
Austin Schuhc3e04282022-02-12 20:00:53 -0800260 method='SLSQP',
Austin Schuh2e28d872022-02-19 18:25:57 -0800261 tol=1e-12,
Austin Schuhc3e04282022-02-12 20:00:53 -0800262 constraints=constraints)
263 print(result)
264
265 return result.x
266
267
268def CatapultProblem():
Austin Schuhac61c882022-02-23 17:22:22 -0800269 c = catapult_lib.Catapult(kCatapultWithBall)
Austin Schuhc3e04282022-02-12 20:00:53 -0800270
271 kHorizon = 40
272
273 u = [0.0] * kHorizon
274 X_initial = numpy.matrix([[0.0], [0.0]])
275 X_final = numpy.matrix([[2.0], [25.0]])
276
Austin Schuhc3e04282022-02-12 20:00:53 -0800277 X_initial = numpy.matrix([[0.0], [0.0]])
278 X = X_initial.copy()
279
280 t_samples = [0.0]
281 x_samples = [0.0]
282 v_samples = [0.0]
283 a_samples = [0.0]
284
285 # Iteratively solve our MPC and simulate it.
286 u_samples = []
287 for i in range(kHorizon):
Austin Schuh2e28d872022-02-19 18:25:57 -0800288 u_horizon = SolveCatapult(c, X, X_final, u)
289
Austin Schuhc3e04282022-02-12 20:00:53 -0800290 u_samples.append(u_horizon[0])
291 v_prior = X[1, 0]
292 X = c.A * X + c.B * numpy.matrix([[u_horizon[0]]])
293 v = X[1, 0]
294 t_samples.append(t_samples[-1] + c.dt)
295 x_samples.append(X[0, 0])
296 v_samples.append(X[1, 0])
297 a_samples.append((v - v_prior) / c.dt)
298
299 u = u_horizon[1:]
300
301 print('Final state', X.transpose())
302 print('Final velocity', X[1, 0] * lever)
303 pylab.subplot(2, 1, 1)
304 pylab.plot(t_samples, x_samples, label="x")
305 pylab.plot(t_samples, v_samples, label="v")
306 pylab.plot(t_samples[1:], u_samples, label="u")
307 pylab.legend()
308 pylab.subplot(2, 1, 2)
309 pylab.plot(t_samples, a_samples, label="a")
310 pylab.legend()
311
312 pylab.show()
Austin Schuh82162452022-02-07 22:01:45 -0800313
314
315def main(argv):
Austin Schuh82162452022-02-07 22:01:45 -0800316 if FLAGS.plot:
Austin Schuhac61c882022-02-23 17:22:22 -0800317 # Do all our math with a lower voltage so we have headroom.
318 U = numpy.matrix([[9.0]])
319
320 prob = osqp.OSQP()
321
322 kHorizon = 40
323 catapult = catapult_lib.Catapult(kCatapultWithBall)
324 X_initial = numpy.matrix([[0.0], [0.0]])
325 X_final = numpy.matrix([[2.0], [25.0]])
326 P, q, c = quadratic_cost(catapult, X_initial, X_final, kHorizon)
327 A = numpy.identity(kHorizon)
328 l = numpy.zeros((kHorizon, 1))
329 u = numpy.ones((kHorizon, 1)) * 12.0
330
331 prob.setup(scipy.sparse.csr_matrix(P),
332 q,
333 scipy.sparse.csr_matrix(A),
334 l,
335 u,
336 warm_start=True)
337
338 result = prob.solve()
339 # Check solver status
340 if result.info.status != 'solved':
341 raise ValueError('OSQP did not solve the problem!')
342
343 # Apply first control input to the plant
344 print(result.x)
345
346 glog.debug("J ball", ball_mass * lever * lever)
347 glog.debug("J bar", J_bar)
348 glog.debug("bar mass", M_bar)
349 glog.debug("J cup", J_cup)
350 glog.debug("cup mass", M_cup)
351 glog.debug("J", J)
352 glog.debug("J Empty", JEmpty)
353
354 glog.debug(
Austin Schuh2e28d872022-02-19 18:25:57 -0800355 "For G:", G, " max speed ",
Austin Schuhac61c882022-02-23 17:22:22 -0800356 catapult_lib.MaxSpeed(params=kCatapultWithBall,
Austin Schuh2e28d872022-02-19 18:25:57 -0800357 U=U,
358 final_position=math.pi / 2.0))
359
360 CatapultProblem()
Austin Schuhac61c882022-02-23 17:22:22 -0800361
362 catapult_lib.PlotStep(params=kCatapultWithBall,
363 R=numpy.matrix([[1.0], [0.0]]))
364 catapult_lib.PlotKick(params=kCatapultWithBall,
365 R=numpy.matrix([[1.0], [0.0]]))
Austin Schuh2e28d872022-02-19 18:25:57 -0800366 return 0
367
Austin Schuhac61c882022-02-23 17:22:22 -0800368 catapult_lib.PlotShot(kCatapultWithBall,
369 U,
370 final_position=math.pi / 4.0)
Austin Schuh82162452022-02-07 22:01:45 -0800371
372 gs = []
373 speed = []
374 for i in numpy.linspace(0.01, 0.15, 150):
Austin Schuhac61c882022-02-23 17:22:22 -0800375 kCatapultWithBall.G = i
376 gs.append(kCatapultWithBall.G)
Austin Schuhc3e04282022-02-12 20:00:53 -0800377 speed.append(
Austin Schuhac61c882022-02-23 17:22:22 -0800378 catapult_lib.MaxSpeed(params=kCatapultWithBall,
Austin Schuhc3e04282022-02-12 20:00:53 -0800379 U=U,
380 final_position=math.pi / 2.0))
381 pylab.plot(gs, speed, label="max_speed")
Austin Schuh82162452022-02-07 22:01:45 -0800382 pylab.show()
Austin Schuh2e28d872022-02-19 18:25:57 -0800383
384 if len(argv) != 5:
385 glog.fatal(
386 'Expected .h file name and .cc file name for the catapult and integral catapult.'
387 )
388 else:
389 namespaces = ['y2022', 'control_loops', 'superstructure', 'catapult']
Ravago Jones5127ccc2022-07-31 16:32:45 -0700390 catapult_lib.WriteCatapult([kCatapultWithBall, kCatapultEmpty],
391 argv[1:3], argv[3:5], namespaces)
Austin Schuh2e28d872022-02-19 18:25:57 -0800392 return 0
Austin Schuh82162452022-02-07 22:01:45 -0800393
394
395if __name__ == '__main__':
396 argv = FLAGS(sys.argv)
Austin Schuhac61c882022-02-23 17:22:22 -0800397 glog.init()
Austin Schuh82162452022-02-07 22:01:45 -0800398 sys.exit(main(argv))