blob: 4d5bbb9014b74c242ef2f4dacdac9ed580915cf8 [file] [log] [blame]
Austin Schuha88c4072016-02-06 14:31:03 -08001#!/usr/bin/python
2
3import numpy
4import sys
5import operator
6
7from frc971.control_loops.python import control_loop
8from frc971.control_loops.python import controls
9
10from y2016.control_loops.python.shoulder import Shoulder, IntegralShoulder
11from y2016.control_loops.python.wrist import Wrist, IntegralWrist
Philipp Schrader1a25ee42016-02-11 07:02:03 +000012from aos.common.util.trapezoid_profile import TrapezoidProfile
Austin Schuha88c4072016-02-06 14:31:03 -080013
14from matplotlib import pylab
15import gflags
16import glog
17
18FLAGS = gflags.FLAGS
19
20try:
21 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
22except gflags.DuplicateFlagError:
23 pass
24
25
26class Arm(control_loop.ControlLoop):
Austin Schuhf59b6bc2016-03-11 21:26:19 -080027 def __init__(self, name="Arm", J=None):
Austin Schuha88c4072016-02-06 14:31:03 -080028 super(Arm, self).__init__(name=name)
Austin Schuhf59b6bc2016-03-11 21:26:19 -080029 self._shoulder = Shoulder(name=name, J=J)
Austin Schuha88c4072016-02-06 14:31:03 -080030 self._shooter = Wrist(name=name)
Austin Schuhf59b6bc2016-03-11 21:26:19 -080031 self.shoulder_Kv = self._shoulder.Kv / self._shoulder.G
Austin Schuha88c4072016-02-06 14:31:03 -080032
33 # Do a coordinate transformation.
34 # X_shooter_grounded = X_shooter + X_shoulder
35 # dX_shooter_grounded/dt = A_shooter * X_shooter + A_shoulder * X_shoulder +
36 # B_shoulder * U_shoulder + B_shooter * U_shooter
37 # dX_shooter_grounded/dt = A_shooter * (X_shooter_grounded - X_shoulder) +
38 # A_shoulder * X_shoulder + B_shooter * U_shooter + B_shoulder * U_shoulder
39 # X = [X_shoulder; X_shooter + X_shoulder]
40 # dX/dt = [A_shoulder 0] [X_shoulder ] + [B_shoulder 0] [U_shoulder]
41 # [(A_shoulder - A_shooter) A_shooter] [X_shooter_grounded] + [B_shoulder B_shooter] [ U_shooter]
42 # Y_shooter_grounded = Y_shooter + Y_shoulder
43
44 self.A_continuous = numpy.matrix(numpy.zeros((4, 4)))
45 self.A_continuous[0:2, 0:2] = self._shoulder.A_continuous
46 self.A_continuous[2:4, 0:2] = (self._shoulder.A_continuous -
47 self._shooter.A_continuous)
48 self.A_continuous[2:4, 2:4] = self._shooter.A_continuous
49
50 self.B_continuous = numpy.matrix(numpy.zeros((4, 2)))
51 self.B_continuous[0:2, 0:1] = self._shoulder.B_continuous
52 self.B_continuous[2:4, 1:2] = self._shooter.B_continuous
53 self.B_continuous[2:4, 0:1] = self._shoulder.B_continuous
54
55 self.C = numpy.matrix(numpy.zeros((2, 4)))
56 self.C[0:1, 0:2] = self._shoulder.C
57 self.C[1:2, 0:2] = -self._shoulder.C
58 self.C[1:2, 2:4] = self._shooter.C
59
60 # D is 0 for all our loops.
61 self.D = numpy.matrix(numpy.zeros((2, 2)))
62
63 self.dt = 0.005
64
65 self.A, self.B = self.ContinuousToDiscrete(
66 self.A_continuous, self.B_continuous, self.dt)
67
68 # Cost of error
69 self.Q = numpy.matrix(numpy.zeros((4, 4)))
70 q_pos_shoulder = 0.014
71 q_vel_shoulder = 4.00
72 q_pos_shooter = 0.014
73 q_vel_shooter = 4.00
74 self.Q[0, 0] = 1.0 / q_pos_shoulder ** 2.0
75 self.Q[1, 1] = 1.0 / q_vel_shoulder ** 2.0
76 self.Q[2, 2] = 1.0 / q_pos_shooter ** 2.0
77 self.Q[3, 3] = 1.0 / q_vel_shooter ** 2.0
78
Austin Schuh2fc10fa2016-02-08 00:44:34 -080079 self.Qff = numpy.matrix(numpy.zeros((4, 4)))
80 qff_pos_shoulder = 0.005
81 qff_vel_shoulder = 1.00
82 qff_pos_shooter = 0.005
83 qff_vel_shooter = 1.00
84 self.Qff[0, 0] = 1.0 / qff_pos_shoulder ** 2.0
85 self.Qff[1, 1] = 1.0 / qff_vel_shoulder ** 2.0
86 self.Qff[2, 2] = 1.0 / qff_pos_shooter ** 2.0
87 self.Qff[3, 3] = 1.0 / qff_vel_shooter ** 2.0
88
Austin Schuha88c4072016-02-06 14:31:03 -080089 # Cost of control effort
90 self.R = numpy.matrix(numpy.zeros((2, 2)))
91 r_voltage = 1.0 / 12.0
92 self.R[0, 0] = r_voltage ** 2.0
93 self.R[1, 1] = r_voltage ** 2.0
94
Austin Schuh2fc10fa2016-02-08 00:44:34 -080095 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
Austin Schuha88c4072016-02-06 14:31:03 -080096
97 glog.debug('Shoulder K')
Austin Schuhf59b6bc2016-03-11 21:26:19 -080098 glog.debug(repr(self._shoulder.K))
99 glog.debug('Poles are %s',
100 repr(numpy.linalg.eig(self._shoulder.A -
101 self._shoulder.B * self._shoulder.K)[0]))
Austin Schuha88c4072016-02-06 14:31:03 -0800102
103 # Compute controller gains.
104 # self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
105 self.K = numpy.matrix(numpy.zeros((2, 4)))
106 self.K[0:1, 0:2] = self._shoulder.K
107 self.K[1:2, 0:2] = (
108 -self.Kff[1:2, 2:4] * self.B[2:4, 0:1] * self._shoulder.K
109 + self.Kff[1:2, 2:4] * self.A[2:4, 0:2])
110 self.K[1:2, 2:4] = self._shooter.K
111
112 glog.debug('Arm controller %s', repr(self.K))
113
114 # Cost of error
115 self.Q = numpy.matrix(numpy.zeros((4, 4)))
116 q_pos_shoulder = 0.05
117 q_vel_shoulder = 2.65
118 q_pos_shooter = 0.05
119 q_vel_shooter = 2.65
120 self.Q[0, 0] = q_pos_shoulder ** 2.0
121 self.Q[1, 1] = q_vel_shoulder ** 2.0
122 self.Q[2, 2] = q_pos_shooter ** 2.0
123 self.Q[3, 3] = q_vel_shooter ** 2.0
124
125 # Cost of control effort
126 self.R = numpy.matrix(numpy.zeros((2, 2)))
127 r_voltage = 0.025
128 self.R[0, 0] = r_voltage ** 2.0
129 self.R[1, 1] = r_voltage ** 2.0
130
131 self.KalmanGain, self.Q_steady = controls.kalman(
132 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
133 self.L = self.A * self.KalmanGain
134
135 self.U_max = numpy.matrix([[12.0], [12.0]])
136 self.U_min = numpy.matrix([[-12.0], [-12.0]])
137
138 self.InitializeState()
139
140
141class IntegralArm(Arm):
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800142 def __init__(self, name="IntegralArm", J=None):
143 super(IntegralArm, self).__init__(name=name, J=J)
Austin Schuha88c4072016-02-06 14:31:03 -0800144
145 self.A_continuous_unaugmented = self.A_continuous
146 self.B_continuous_unaugmented = self.B_continuous
147
148 self.A_continuous = numpy.matrix(numpy.zeros((6, 6)))
149 self.A_continuous[0:4, 0:4] = self.A_continuous_unaugmented
150 self.A_continuous[0:4, 4:6] = self.B_continuous_unaugmented
151
152 self.B_continuous = numpy.matrix(numpy.zeros((6, 2)))
153 self.B_continuous[0:4, 0:2] = self.B_continuous_unaugmented
154
155 self.C_unaugmented = self.C
156 self.C = numpy.matrix(numpy.zeros((2, 6)))
157 self.C[0:2, 0:4] = self.C_unaugmented
158
159 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
160
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800161 q_pos_shoulder = 0.10
162 q_vel_shoulder = 0.005
163 q_voltage_shoulder = 3.5
Austin Schuha88c4072016-02-06 14:31:03 -0800164 q_pos_shooter = 0.08
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800165 q_vel_shooter = 2.00
Austin Schuh1aa5ee92016-02-28 21:57:45 -0800166 q_voltage_shooter = 1.0
Austin Schuha88c4072016-02-06 14:31:03 -0800167 self.Q = numpy.matrix(numpy.zeros((6, 6)))
168 self.Q[0, 0] = q_pos_shoulder ** 2.0
169 self.Q[1, 1] = q_vel_shoulder ** 2.0
170 self.Q[2, 2] = q_pos_shooter ** 2.0
171 self.Q[3, 3] = q_vel_shooter ** 2.0
172 self.Q[4, 4] = q_voltage_shoulder ** 2.0
173 self.Q[5, 5] = q_voltage_shooter ** 2.0
174
175 self.R = numpy.matrix(numpy.zeros((2, 2)))
176 r_pos = 0.05
177 self.R[0, 0] = r_pos ** 2.0
178 self.R[1, 1] = r_pos ** 2.0
179
180 self.KalmanGain, self.Q_steady = controls.kalman(
181 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
182 self.L = self.A * self.KalmanGain
183
184 self.K_unaugmented = self.K
185 self.K = numpy.matrix(numpy.zeros((2, 6)))
186 self.K[0:2, 0:4] = self.K_unaugmented
187 self.K[0, 4] = 1
188 self.K[1, 5] = 1
189
190 self.Kff = numpy.concatenate((self.Kff, numpy.matrix(numpy.zeros((2, 2)))), axis=1)
191
192 self.InitializeState()
193
194
195class ScenarioPlotter(object):
196 def __init__(self):
197 # Various lists for graphing things.
198 self.t = []
199 self.x_shoulder = []
200 self.v_shoulder = []
201 self.a_shoulder = []
202 self.x_hat_shoulder = []
203 self.u_shoulder = []
204 self.offset_shoulder = []
205 self.x_shooter = []
206 self.v_shooter = []
207 self.a_shooter = []
208 self.x_hat_shooter = []
209 self.u_shooter = []
210 self.offset_shooter = []
211 self.goal_x_shoulder = []
212 self.goal_v_shoulder = []
213 self.goal_x_shooter = []
214 self.goal_v_shooter = []
215
216 def run_test(self, arm, end_goal,
217 iterations=200, controller=None, observer=None):
218 """Runs the plant with an initial condition and goal.
219
220 Args:
221 arm: Arm object to use.
222 end_goal: numpy.Matrix[6, 1], end goal state.
223 iterations: Number of timesteps to run the model for.
224 controller: Arm object to get K from, or None if we should
225 use arm.
226 observer: Arm object to use for the observer, or None if we should
227 use the actual state.
228 """
229
230 if controller is None:
231 controller = arm
232
233 vbat = 12.0
234
235 if self.t:
236 initial_t = self.t[-1] + arm.dt
237 else:
238 initial_t = 0
239
240 goal = numpy.concatenate((arm.X, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
Austin Schuha88c4072016-02-06 14:31:03 -0800241
Philipp Schrader1a25ee42016-02-11 07:02:03 +0000242 shoulder_profile = TrapezoidProfile(arm.dt)
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800243 shoulder_profile.set_maximum_acceleration(12.0)
Austin Schuha88c4072016-02-06 14:31:03 -0800244 shoulder_profile.set_maximum_velocity(10.0)
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800245 shoulder_profile.SetGoal(goal[0, 0])
Philipp Schrader1a25ee42016-02-11 07:02:03 +0000246 shooter_profile = TrapezoidProfile(arm.dt)
Austin Schuha88c4072016-02-06 14:31:03 -0800247 shooter_profile.set_maximum_acceleration(50.0)
248 shooter_profile.set_maximum_velocity(10.0)
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800249 shooter_profile.SetGoal(goal[2, 0])
Austin Schuha88c4072016-02-06 14:31:03 -0800250
251 U_last = numpy.matrix(numpy.zeros((2, 1)))
252 for i in xrange(iterations):
253 X_hat = arm.X
254
255 if observer is not None:
256 observer.Y = arm.Y
257 observer.CorrectObserver(U_last)
258 self.offset_shoulder.append(observer.X_hat[4, 0])
259 self.offset_shooter.append(observer.X_hat[5, 0])
260
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800261 X_hat = observer.X_hat
262 self.x_hat_shoulder.append(observer.X_hat[0, 0])
263 self.x_hat_shooter.append(observer.X_hat[2, 0])
264
Austin Schuha88c4072016-02-06 14:31:03 -0800265 next_shoulder_goal = shoulder_profile.Update(end_goal[0, 0], end_goal[1, 0])
266 next_shooter_goal = shooter_profile.Update(end_goal[2, 0], end_goal[3, 0])
267
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800268 next_goal = numpy.concatenate(
269 (next_shoulder_goal,
270 next_shooter_goal,
271 numpy.matrix(numpy.zeros((2, 1)))),
272 axis=0)
Austin Schuha88c4072016-02-06 14:31:03 -0800273 self.goal_x_shoulder.append(goal[0, 0])
274 self.goal_v_shoulder.append(goal[1, 0])
275 self.goal_x_shooter.append(goal[2, 0])
276 self.goal_v_shooter.append(goal[3, 0])
277
278 ff_U = controller.Kff * (next_goal - observer.A * goal)
279
Austin Schuha88c4072016-02-06 14:31:03 -0800280 U_uncapped = controller.K * (goal - X_hat) + ff_U
281 U = U_uncapped.copy()
282
283 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
284 U[1, 0] = numpy.clip(U[1, 0], -vbat, vbat)
285 self.x_shoulder.append(arm.X[0, 0])
286 self.x_shooter.append(arm.X[2, 0])
287
288 if self.v_shoulder:
289 last_v_shoulder = self.v_shoulder[-1]
290 else:
291 last_v_shoulder = 0
292 self.v_shoulder.append(arm.X[1, 0])
293 self.a_shoulder.append(
294 (self.v_shoulder[-1] - last_v_shoulder) / arm.dt)
295
296 if self.v_shooter:
297 last_v_shooter = self.v_shooter[-1]
298 else:
299 last_v_shooter = 0
300 self.v_shooter.append(arm.X[3, 0])
301 self.a_shooter.append(
302 (self.v_shooter[-1] - last_v_shooter) / arm.dt)
303
304 if i % 40 == 0:
305 # Test that if we move the shoulder, the shooter stays perfect.
306 #observer.X_hat[0, 0] += 0.20
307 #arm.X[0, 0] += 0.20
308 pass
Austin Schuh1aa5ee92016-02-28 21:57:45 -0800309 U_error = numpy.matrix([[2.0], [2.0]])
Austin Schuha88c4072016-02-06 14:31:03 -0800310 # Kick it and see what happens.
311 #if (initial_t + i * arm.dt) % 0.4 > 0.2:
312 #U_error = numpy.matrix([[4.0], [0.0]])
313 #else:
314 #U_error = numpy.matrix([[-4.0], [0.0]])
315
316 arm.Update(U + U_error)
317
318 if observer is not None:
319 observer.PredictObserver(U)
320
321 self.t.append(initial_t + i * arm.dt)
322 self.u_shoulder.append(U[0, 0])
323 self.u_shooter.append(U[1, 0])
324
Austin Schuha88c4072016-02-06 14:31:03 -0800325 ff_U -= U_uncapped - U
326 goal = controller.A * goal + controller.B * ff_U
327
328 if U[0, 0] != U_uncapped[0, 0]:
329 glog.debug('Moving shoulder %s', repr(initial_t + i * arm.dt))
330 glog.debug('U error %s', repr(U_uncapped - U))
331 glog.debug('goal change is %s',
332 repr(next_shoulder_goal -
333 numpy.matrix([[goal[0, 0]], [goal[1, 0]]])))
334 shoulder_profile.MoveCurrentState(
335 numpy.matrix([[goal[0, 0]], [goal[1, 0]]]))
336 if U[1, 0] != U_uncapped[1, 0]:
337 glog.debug('Moving shooter %s', repr(initial_t + i * arm.dt))
338 glog.debug('U error %s', repr(U_uncapped - U))
339 shooter_profile.MoveCurrentState(
340 numpy.matrix([[goal[2, 0]], [goal[3, 0]]]))
341 U_last = U
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800342 glog.debug('goal_error %s', repr(end_goal - goal))
343 glog.debug('error %s', repr(observer.X_hat - end_goal))
Austin Schuha88c4072016-02-06 14:31:03 -0800344
345
346 def Plot(self):
347 pylab.subplot(3, 1, 1)
348 pylab.plot(self.t, self.x_shoulder, label='x shoulder')
349 pylab.plot(self.t, self.goal_x_shoulder, label='goal x shoulder')
350 pylab.plot(self.t, self.x_hat_shoulder, label='x_hat shoulder')
351
352 pylab.plot(self.t, self.x_shooter, label='x shooter')
353 pylab.plot(self.t, self.x_hat_shooter, label='x_hat shooter')
354 pylab.plot(self.t, self.goal_x_shooter, label='goal x shooter')
355 pylab.plot(self.t, map(operator.add, self.x_shooter, self.x_shoulder),
356 label='x shooter ground')
357 pylab.plot(self.t, map(operator.add, self.x_hat_shooter, self.x_hat_shoulder),
358 label='x_hat shooter ground')
359 pylab.legend()
360
361 pylab.subplot(3, 1, 2)
362 pylab.plot(self.t, self.u_shoulder, label='u shoulder')
363 pylab.plot(self.t, self.offset_shoulder, label='voltage_offset shoulder')
364 pylab.plot(self.t, self.u_shooter, label='u shooter')
365 pylab.plot(self.t, self.offset_shooter, label='voltage_offset shooter')
366 pylab.legend()
367
368 pylab.subplot(3, 1, 3)
369 pylab.plot(self.t, self.a_shoulder, label='a_shoulder')
370 pylab.plot(self.t, self.a_shooter, label='a_shooter')
371 pylab.legend()
372
373 pylab.show()
374
375
376def main(argv):
377 argv = FLAGS(argv)
378 glog.init()
379
380 scenario_plotter = ScenarioPlotter()
381
Diana Vandenberg9cc9ab62016-04-20 21:27:47 -0700382 J_accelerating = 15
Austin Schuh6ca0f792016-03-12 14:06:14 -0800383 J_decelerating = 5
384
385 arm = Arm(name='AcceleratingArm', J=J_accelerating)
386 arm_integral_controller = IntegralArm(
387 name='AcceleratingIntegralArm', J=J_accelerating)
Austin Schuha88c4072016-02-06 14:31:03 -0800388 arm_observer = IntegralArm()
389
390 # Test moving the shoulder with constant separation.
391 initial_X = numpy.matrix([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])
392 R = numpy.matrix([[numpy.pi / 2.0],
393 [0.0],
394 [0.0], #[numpy.pi / 2.0],
395 [0.0],
396 [0.0],
397 [0.0]])
398 arm.X = initial_X[0:4, 0]
399 arm_observer.X = initial_X
400
401 scenario_plotter.run_test(arm=arm,
402 end_goal=R,
403 iterations=300,
Austin Schuh6ca0f792016-03-12 14:06:14 -0800404 controller=arm_integral_controller,
Austin Schuha88c4072016-02-06 14:31:03 -0800405 observer=arm_observer)
406
407 if len(argv) != 5:
408 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
409 else:
410 namespaces = ['y2016', 'control_loops', 'superstructure']
Austin Schuh6ca0f792016-03-12 14:06:14 -0800411 decelerating_arm = Arm(name='DeceleratingArm', J=J_decelerating)
412 loop_writer = control_loop.ControlLoopWriter(
413 'Arm', [arm, decelerating_arm], namespaces=namespaces)
Austin Schuha88c4072016-02-06 14:31:03 -0800414 loop_writer.Write(argv[1], argv[2])
415
Austin Schuh6ca0f792016-03-12 14:06:14 -0800416 decelerating_integral_arm_controller = IntegralArm(
417 name='DeceleratingIntegralArm', J=J_decelerating)
418
Austin Schuha88c4072016-02-06 14:31:03 -0800419 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuh6ca0f792016-03-12 14:06:14 -0800420 'IntegralArm',
421 [arm_integral_controller, decelerating_integral_arm_controller],
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800422 namespaces=namespaces)
423 integral_loop_writer.AddConstant(control_loop.Constant("kV_shoulder", "%f",
Austin Schuh6ca0f792016-03-12 14:06:14 -0800424 arm_integral_controller.shoulder_Kv))
Austin Schuha88c4072016-02-06 14:31:03 -0800425 integral_loop_writer.Write(argv[3], argv[4])
426
427 if FLAGS.plot:
428 scenario_plotter.Plot()
429
430if __name__ == '__main__':
431 sys.exit(main(sys.argv))