blob: 28a704d45b88d6bfec6ec8f89d3d5c42d0f1b4c7 [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
12from aos.common.util.trapezoid_profile import TrapizoidProfile
13
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):
27 def __init__(self, name="Arm"):
28 super(Arm, self).__init__(name=name)
29 self._shoulder = Shoulder(name=name)
30 self._shooter = Wrist(name=name)
31
32 # Do a coordinate transformation.
33 # X_shooter_grounded = X_shooter + X_shoulder
34 # dX_shooter_grounded/dt = A_shooter * X_shooter + A_shoulder * X_shoulder +
35 # B_shoulder * U_shoulder + B_shooter * U_shooter
36 # dX_shooter_grounded/dt = A_shooter * (X_shooter_grounded - X_shoulder) +
37 # A_shoulder * X_shoulder + B_shooter * U_shooter + B_shoulder * U_shoulder
38 # X = [X_shoulder; X_shooter + X_shoulder]
39 # dX/dt = [A_shoulder 0] [X_shoulder ] + [B_shoulder 0] [U_shoulder]
40 # [(A_shoulder - A_shooter) A_shooter] [X_shooter_grounded] + [B_shoulder B_shooter] [ U_shooter]
41 # Y_shooter_grounded = Y_shooter + Y_shoulder
42
43 self.A_continuous = numpy.matrix(numpy.zeros((4, 4)))
44 self.A_continuous[0:2, 0:2] = self._shoulder.A_continuous
45 self.A_continuous[2:4, 0:2] = (self._shoulder.A_continuous -
46 self._shooter.A_continuous)
47 self.A_continuous[2:4, 2:4] = self._shooter.A_continuous
48
49 self.B_continuous = numpy.matrix(numpy.zeros((4, 2)))
50 self.B_continuous[0:2, 0:1] = self._shoulder.B_continuous
51 self.B_continuous[2:4, 1:2] = self._shooter.B_continuous
52 self.B_continuous[2:4, 0:1] = self._shoulder.B_continuous
53
54 self.C = numpy.matrix(numpy.zeros((2, 4)))
55 self.C[0:1, 0:2] = self._shoulder.C
56 self.C[1:2, 0:2] = -self._shoulder.C
57 self.C[1:2, 2:4] = self._shooter.C
58
59 # D is 0 for all our loops.
60 self.D = numpy.matrix(numpy.zeros((2, 2)))
61
62 self.dt = 0.005
63
64 self.A, self.B = self.ContinuousToDiscrete(
65 self.A_continuous, self.B_continuous, self.dt)
66
67 # Cost of error
68 self.Q = numpy.matrix(numpy.zeros((4, 4)))
69 q_pos_shoulder = 0.014
70 q_vel_shoulder = 4.00
71 q_pos_shooter = 0.014
72 q_vel_shooter = 4.00
73 self.Q[0, 0] = 1.0 / q_pos_shoulder ** 2.0
74 self.Q[1, 1] = 1.0 / q_vel_shoulder ** 2.0
75 self.Q[2, 2] = 1.0 / q_pos_shooter ** 2.0
76 self.Q[3, 3] = 1.0 / q_vel_shooter ** 2.0
77
78 # Cost of control effort
79 self.R = numpy.matrix(numpy.zeros((2, 2)))
80 r_voltage = 1.0 / 12.0
81 self.R[0, 0] = r_voltage ** 2.0
82 self.R[1, 1] = r_voltage ** 2.0
83
84 self.Kff = controls.TwoStateFeedForwards(self.B, self.Q)
85
86 glog.debug('Shoulder K')
87 glog.debug(self._shoulder.K)
88
89 # Compute controller gains.
90 # self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
91 self.K = numpy.matrix(numpy.zeros((2, 4)))
92 self.K[0:1, 0:2] = self._shoulder.K
93 self.K[1:2, 0:2] = (
94 -self.Kff[1:2, 2:4] * self.B[2:4, 0:1] * self._shoulder.K
95 + self.Kff[1:2, 2:4] * self.A[2:4, 0:2])
96 self.K[1:2, 2:4] = self._shooter.K
97
98 glog.debug('Arm controller %s', repr(self.K))
99
100 # Cost of error
101 self.Q = numpy.matrix(numpy.zeros((4, 4)))
102 q_pos_shoulder = 0.05
103 q_vel_shoulder = 2.65
104 q_pos_shooter = 0.05
105 q_vel_shooter = 2.65
106 self.Q[0, 0] = q_pos_shoulder ** 2.0
107 self.Q[1, 1] = q_vel_shoulder ** 2.0
108 self.Q[2, 2] = q_pos_shooter ** 2.0
109 self.Q[3, 3] = q_vel_shooter ** 2.0
110
111 # Cost of control effort
112 self.R = numpy.matrix(numpy.zeros((2, 2)))
113 r_voltage = 0.025
114 self.R[0, 0] = r_voltage ** 2.0
115 self.R[1, 1] = r_voltage ** 2.0
116
117 self.KalmanGain, self.Q_steady = controls.kalman(
118 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
119 self.L = self.A * self.KalmanGain
120
121 self.U_max = numpy.matrix([[12.0], [12.0]])
122 self.U_min = numpy.matrix([[-12.0], [-12.0]])
123
124 self.InitializeState()
125
126
127class IntegralArm(Arm):
128 def __init__(self, name="IntegralArm"):
129 super(IntegralArm, self).__init__(name=name)
130
131 self.A_continuous_unaugmented = self.A_continuous
132 self.B_continuous_unaugmented = self.B_continuous
133
134 self.A_continuous = numpy.matrix(numpy.zeros((6, 6)))
135 self.A_continuous[0:4, 0:4] = self.A_continuous_unaugmented
136 self.A_continuous[0:4, 4:6] = self.B_continuous_unaugmented
137
138 self.B_continuous = numpy.matrix(numpy.zeros((6, 2)))
139 self.B_continuous[0:4, 0:2] = self.B_continuous_unaugmented
140
141 self.C_unaugmented = self.C
142 self.C = numpy.matrix(numpy.zeros((2, 6)))
143 self.C[0:2, 0:4] = self.C_unaugmented
144
145 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
146
147 q_pos_shoulder = 0.08
148 q_vel_shoulder = 4.00
149 q_voltage_shoulder = 6.0
150 q_pos_shooter = 0.08
151 q_vel_shooter = 4.00
152 q_voltage_shooter = 6.0
153 self.Q = numpy.matrix(numpy.zeros((6, 6)))
154 self.Q[0, 0] = q_pos_shoulder ** 2.0
155 self.Q[1, 1] = q_vel_shoulder ** 2.0
156 self.Q[2, 2] = q_pos_shooter ** 2.0
157 self.Q[3, 3] = q_vel_shooter ** 2.0
158 self.Q[4, 4] = q_voltage_shoulder ** 2.0
159 self.Q[5, 5] = q_voltage_shooter ** 2.0
160
161 self.R = numpy.matrix(numpy.zeros((2, 2)))
162 r_pos = 0.05
163 self.R[0, 0] = r_pos ** 2.0
164 self.R[1, 1] = r_pos ** 2.0
165
166 self.KalmanGain, self.Q_steady = controls.kalman(
167 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
168 self.L = self.A * self.KalmanGain
169
170 self.K_unaugmented = self.K
171 self.K = numpy.matrix(numpy.zeros((2, 6)))
172 self.K[0:2, 0:4] = self.K_unaugmented
173 self.K[0, 4] = 1
174 self.K[1, 5] = 1
175
176 self.Kff = numpy.concatenate((self.Kff, numpy.matrix(numpy.zeros((2, 2)))), axis=1)
177
178 self.InitializeState()
179
180
181class ScenarioPlotter(object):
182 def __init__(self):
183 # Various lists for graphing things.
184 self.t = []
185 self.x_shoulder = []
186 self.v_shoulder = []
187 self.a_shoulder = []
188 self.x_hat_shoulder = []
189 self.u_shoulder = []
190 self.offset_shoulder = []
191 self.x_shooter = []
192 self.v_shooter = []
193 self.a_shooter = []
194 self.x_hat_shooter = []
195 self.u_shooter = []
196 self.offset_shooter = []
197 self.goal_x_shoulder = []
198 self.goal_v_shoulder = []
199 self.goal_x_shooter = []
200 self.goal_v_shooter = []
201
202 def run_test(self, arm, end_goal,
203 iterations=200, controller=None, observer=None):
204 """Runs the plant with an initial condition and goal.
205
206 Args:
207 arm: Arm object to use.
208 end_goal: numpy.Matrix[6, 1], end goal state.
209 iterations: Number of timesteps to run the model for.
210 controller: Arm object to get K from, or None if we should
211 use arm.
212 observer: Arm object to use for the observer, or None if we should
213 use the actual state.
214 """
215
216 if controller is None:
217 controller = arm
218
219 vbat = 12.0
220
221 if self.t:
222 initial_t = self.t[-1] + arm.dt
223 else:
224 initial_t = 0
225
226 goal = numpy.concatenate((arm.X, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
227 current_shoulder_goal = goal[0:2, 0].copy()
228 current_shooter_goal = goal[2:4, 0].copy()
229
230 shoulder_profile = TrapizoidProfile(arm.dt)
231 shoulder_profile.set_maximum_acceleration(50.0)
232 shoulder_profile.set_maximum_velocity(10.0)
233 shoulder_profile.SetGoal(current_shoulder_goal[0, 0])
234 shooter_profile = TrapizoidProfile(arm.dt)
235 shooter_profile.set_maximum_acceleration(50.0)
236 shooter_profile.set_maximum_velocity(10.0)
237 shooter_profile.SetGoal(current_shooter_goal[0, 0])
238
239 U_last = numpy.matrix(numpy.zeros((2, 1)))
240 for i in xrange(iterations):
241 X_hat = arm.X
242
243 if observer is not None:
244 observer.Y = arm.Y
245 observer.CorrectObserver(U_last)
246 self.offset_shoulder.append(observer.X_hat[4, 0])
247 self.offset_shooter.append(observer.X_hat[5, 0])
248
249 next_shoulder_goal = shoulder_profile.Update(end_goal[0, 0], end_goal[1, 0])
250 next_shooter_goal = shooter_profile.Update(end_goal[2, 0], end_goal[3, 0])
251
252 next_goal = numpy.concatenate((next_shoulder_goal, next_shooter_goal, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
253 self.goal_x_shoulder.append(goal[0, 0])
254 self.goal_v_shoulder.append(goal[1, 0])
255 self.goal_x_shooter.append(goal[2, 0])
256 self.goal_v_shooter.append(goal[3, 0])
257
258 ff_U = controller.Kff * (next_goal - observer.A * goal)
259
260 if observer is not None:
261 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
265 U_uncapped = controller.K * (goal - X_hat) + ff_U
266 U = U_uncapped.copy()
267
268 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
269 U[1, 0] = numpy.clip(U[1, 0], -vbat, vbat)
270 self.x_shoulder.append(arm.X[0, 0])
271 self.x_shooter.append(arm.X[2, 0])
272
273 if self.v_shoulder:
274 last_v_shoulder = self.v_shoulder[-1]
275 else:
276 last_v_shoulder = 0
277 self.v_shoulder.append(arm.X[1, 0])
278 self.a_shoulder.append(
279 (self.v_shoulder[-1] - last_v_shoulder) / arm.dt)
280
281 if self.v_shooter:
282 last_v_shooter = self.v_shooter[-1]
283 else:
284 last_v_shooter = 0
285 self.v_shooter.append(arm.X[3, 0])
286 self.a_shooter.append(
287 (self.v_shooter[-1] - last_v_shooter) / arm.dt)
288
289 if i % 40 == 0:
290 # Test that if we move the shoulder, the shooter stays perfect.
291 #observer.X_hat[0, 0] += 0.20
292 #arm.X[0, 0] += 0.20
293 pass
294 U_error = numpy.matrix([[0.0], [0.0]])
295 # Kick it and see what happens.
296 #if (initial_t + i * arm.dt) % 0.4 > 0.2:
297 #U_error = numpy.matrix([[4.0], [0.0]])
298 #else:
299 #U_error = numpy.matrix([[-4.0], [0.0]])
300
301 arm.Update(U + U_error)
302
303 if observer is not None:
304 observer.PredictObserver(U)
305
306 self.t.append(initial_t + i * arm.dt)
307 self.u_shoulder.append(U[0, 0])
308 self.u_shooter.append(U[1, 0])
309
310 glog.debug('Time: %f', self.t[-1])
311
312 ff_U -= U_uncapped - U
313 goal = controller.A * goal + controller.B * ff_U
314
315 if U[0, 0] != U_uncapped[0, 0]:
316 glog.debug('Moving shoulder %s', repr(initial_t + i * arm.dt))
317 glog.debug('U error %s', repr(U_uncapped - U))
318 glog.debug('goal change is %s',
319 repr(next_shoulder_goal -
320 numpy.matrix([[goal[0, 0]], [goal[1, 0]]])))
321 shoulder_profile.MoveCurrentState(
322 numpy.matrix([[goal[0, 0]], [goal[1, 0]]]))
323 if U[1, 0] != U_uncapped[1, 0]:
324 glog.debug('Moving shooter %s', repr(initial_t + i * arm.dt))
325 glog.debug('U error %s', repr(U_uncapped - U))
326 shooter_profile.MoveCurrentState(
327 numpy.matrix([[goal[2, 0]], [goal[3, 0]]]))
328 U_last = U
329 glog.debug('End goal is %s', repr(end_goal))
330 glog.debug('last goal is %s', repr(goal))
331 glog.debug('End state is %s', repr(arm.X))
332
333
334 def Plot(self):
335 pylab.subplot(3, 1, 1)
336 pylab.plot(self.t, self.x_shoulder, label='x shoulder')
337 pylab.plot(self.t, self.goal_x_shoulder, label='goal x shoulder')
338 pylab.plot(self.t, self.x_hat_shoulder, label='x_hat shoulder')
339
340 pylab.plot(self.t, self.x_shooter, label='x shooter')
341 pylab.plot(self.t, self.x_hat_shooter, label='x_hat shooter')
342 pylab.plot(self.t, self.goal_x_shooter, label='goal x shooter')
343 pylab.plot(self.t, map(operator.add, self.x_shooter, self.x_shoulder),
344 label='x shooter ground')
345 pylab.plot(self.t, map(operator.add, self.x_hat_shooter, self.x_hat_shoulder),
346 label='x_hat shooter ground')
347 pylab.legend()
348
349 pylab.subplot(3, 1, 2)
350 pylab.plot(self.t, self.u_shoulder, label='u shoulder')
351 pylab.plot(self.t, self.offset_shoulder, label='voltage_offset shoulder')
352 pylab.plot(self.t, self.u_shooter, label='u shooter')
353 pylab.plot(self.t, self.offset_shooter, label='voltage_offset shooter')
354 pylab.legend()
355
356 pylab.subplot(3, 1, 3)
357 pylab.plot(self.t, self.a_shoulder, label='a_shoulder')
358 pylab.plot(self.t, self.a_shooter, label='a_shooter')
359 pylab.legend()
360
361 pylab.show()
362
363
364def main(argv):
365 argv = FLAGS(argv)
366 glog.init()
367
368 scenario_plotter = ScenarioPlotter()
369
370 arm = Arm()
371 arm_controller = IntegralArm()
372 arm_observer = IntegralArm()
373
374 # Test moving the shoulder with constant separation.
375 initial_X = numpy.matrix([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])
376 R = numpy.matrix([[numpy.pi / 2.0],
377 [0.0],
378 [0.0], #[numpy.pi / 2.0],
379 [0.0],
380 [0.0],
381 [0.0]])
382 arm.X = initial_X[0:4, 0]
383 arm_observer.X = initial_X
384
385 scenario_plotter.run_test(arm=arm,
386 end_goal=R,
387 iterations=300,
388 controller=arm_controller,
389 observer=arm_observer)
390
391 if len(argv) != 5:
392 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
393 else:
394 namespaces = ['y2016', 'control_loops', 'superstructure']
395 loop_writer = control_loop.ControlLoopWriter('Arm', [arm],
396 namespaces=namespaces)
397 loop_writer.Write(argv[1], argv[2])
398
399 integral_loop_writer = control_loop.ControlLoopWriter(
400 'IntegralArm', [arm_controller], namespaces=namespaces)
401 integral_loop_writer.Write(argv[3], argv[4])
402
403 if FLAGS.plot:
404 scenario_plotter.Plot()
405
406if __name__ == '__main__':
407 sys.exit(main(sys.argv))