blob: ae14b04c00a9ec0a22dc131823ced733f90da66d [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):
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
Austin Schuh2fc10fa2016-02-08 00:44:34 -080078 self.Qff = numpy.matrix(numpy.zeros((4, 4)))
79 qff_pos_shoulder = 0.005
80 qff_vel_shoulder = 1.00
81 qff_pos_shooter = 0.005
82 qff_vel_shooter = 1.00
83 self.Qff[0, 0] = 1.0 / qff_pos_shoulder ** 2.0
84 self.Qff[1, 1] = 1.0 / qff_vel_shoulder ** 2.0
85 self.Qff[2, 2] = 1.0 / qff_pos_shooter ** 2.0
86 self.Qff[3, 3] = 1.0 / qff_vel_shooter ** 2.0
87
Austin Schuha88c4072016-02-06 14:31:03 -080088 # Cost of control effort
89 self.R = numpy.matrix(numpy.zeros((2, 2)))
90 r_voltage = 1.0 / 12.0
91 self.R[0, 0] = r_voltage ** 2.0
92 self.R[1, 1] = r_voltage ** 2.0
93
Austin Schuh2fc10fa2016-02-08 00:44:34 -080094 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
Austin Schuha88c4072016-02-06 14:31:03 -080095
96 glog.debug('Shoulder K')
97 glog.debug(self._shoulder.K)
98
99 # Compute controller gains.
100 # self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
101 self.K = numpy.matrix(numpy.zeros((2, 4)))
102 self.K[0:1, 0:2] = self._shoulder.K
103 self.K[1:2, 0:2] = (
104 -self.Kff[1:2, 2:4] * self.B[2:4, 0:1] * self._shoulder.K
105 + self.Kff[1:2, 2:4] * self.A[2:4, 0:2])
106 self.K[1:2, 2:4] = self._shooter.K
107
108 glog.debug('Arm controller %s', repr(self.K))
109
110 # Cost of error
111 self.Q = numpy.matrix(numpy.zeros((4, 4)))
112 q_pos_shoulder = 0.05
113 q_vel_shoulder = 2.65
114 q_pos_shooter = 0.05
115 q_vel_shooter = 2.65
116 self.Q[0, 0] = q_pos_shoulder ** 2.0
117 self.Q[1, 1] = q_vel_shoulder ** 2.0
118 self.Q[2, 2] = q_pos_shooter ** 2.0
119 self.Q[3, 3] = q_vel_shooter ** 2.0
120
121 # Cost of control effort
122 self.R = numpy.matrix(numpy.zeros((2, 2)))
123 r_voltage = 0.025
124 self.R[0, 0] = r_voltage ** 2.0
125 self.R[1, 1] = r_voltage ** 2.0
126
127 self.KalmanGain, self.Q_steady = controls.kalman(
128 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
129 self.L = self.A * self.KalmanGain
130
131 self.U_max = numpy.matrix([[12.0], [12.0]])
132 self.U_min = numpy.matrix([[-12.0], [-12.0]])
133
134 self.InitializeState()
135
136
137class IntegralArm(Arm):
138 def __init__(self, name="IntegralArm"):
139 super(IntegralArm, self).__init__(name=name)
140
141 self.A_continuous_unaugmented = self.A_continuous
142 self.B_continuous_unaugmented = self.B_continuous
143
144 self.A_continuous = numpy.matrix(numpy.zeros((6, 6)))
145 self.A_continuous[0:4, 0:4] = self.A_continuous_unaugmented
146 self.A_continuous[0:4, 4:6] = self.B_continuous_unaugmented
147
148 self.B_continuous = numpy.matrix(numpy.zeros((6, 2)))
149 self.B_continuous[0:4, 0:2] = self.B_continuous_unaugmented
150
151 self.C_unaugmented = self.C
152 self.C = numpy.matrix(numpy.zeros((2, 6)))
153 self.C[0:2, 0:4] = self.C_unaugmented
154
155 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
156
157 q_pos_shoulder = 0.08
158 q_vel_shoulder = 4.00
159 q_voltage_shoulder = 6.0
160 q_pos_shooter = 0.08
161 q_vel_shooter = 4.00
162 q_voltage_shooter = 6.0
163 self.Q = numpy.matrix(numpy.zeros((6, 6)))
164 self.Q[0, 0] = q_pos_shoulder ** 2.0
165 self.Q[1, 1] = q_vel_shoulder ** 2.0
166 self.Q[2, 2] = q_pos_shooter ** 2.0
167 self.Q[3, 3] = q_vel_shooter ** 2.0
168 self.Q[4, 4] = q_voltage_shoulder ** 2.0
169 self.Q[5, 5] = q_voltage_shooter ** 2.0
170
171 self.R = numpy.matrix(numpy.zeros((2, 2)))
172 r_pos = 0.05
173 self.R[0, 0] = r_pos ** 2.0
174 self.R[1, 1] = r_pos ** 2.0
175
176 self.KalmanGain, self.Q_steady = controls.kalman(
177 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
178 self.L = self.A * self.KalmanGain
179
180 self.K_unaugmented = self.K
181 self.K = numpy.matrix(numpy.zeros((2, 6)))
182 self.K[0:2, 0:4] = self.K_unaugmented
183 self.K[0, 4] = 1
184 self.K[1, 5] = 1
185
186 self.Kff = numpy.concatenate((self.Kff, numpy.matrix(numpy.zeros((2, 2)))), axis=1)
187
188 self.InitializeState()
189
190
191class ScenarioPlotter(object):
192 def __init__(self):
193 # Various lists for graphing things.
194 self.t = []
195 self.x_shoulder = []
196 self.v_shoulder = []
197 self.a_shoulder = []
198 self.x_hat_shoulder = []
199 self.u_shoulder = []
200 self.offset_shoulder = []
201 self.x_shooter = []
202 self.v_shooter = []
203 self.a_shooter = []
204 self.x_hat_shooter = []
205 self.u_shooter = []
206 self.offset_shooter = []
207 self.goal_x_shoulder = []
208 self.goal_v_shoulder = []
209 self.goal_x_shooter = []
210 self.goal_v_shooter = []
211
212 def run_test(self, arm, end_goal,
213 iterations=200, controller=None, observer=None):
214 """Runs the plant with an initial condition and goal.
215
216 Args:
217 arm: Arm object to use.
218 end_goal: numpy.Matrix[6, 1], end goal state.
219 iterations: Number of timesteps to run the model for.
220 controller: Arm object to get K from, or None if we should
221 use arm.
222 observer: Arm object to use for the observer, or None if we should
223 use the actual state.
224 """
225
226 if controller is None:
227 controller = arm
228
229 vbat = 12.0
230
231 if self.t:
232 initial_t = self.t[-1] + arm.dt
233 else:
234 initial_t = 0
235
236 goal = numpy.concatenate((arm.X, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
Austin Schuha88c4072016-02-06 14:31:03 -0800237
Philipp Schrader1a25ee42016-02-11 07:02:03 +0000238 shoulder_profile = TrapezoidProfile(arm.dt)
Austin Schuha88c4072016-02-06 14:31:03 -0800239 shoulder_profile.set_maximum_acceleration(50.0)
240 shoulder_profile.set_maximum_velocity(10.0)
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800241 shoulder_profile.SetGoal(goal[0, 0])
Philipp Schrader1a25ee42016-02-11 07:02:03 +0000242 shooter_profile = TrapezoidProfile(arm.dt)
Austin Schuha88c4072016-02-06 14:31:03 -0800243 shooter_profile.set_maximum_acceleration(50.0)
244 shooter_profile.set_maximum_velocity(10.0)
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800245 shooter_profile.SetGoal(goal[2, 0])
Austin Schuha88c4072016-02-06 14:31:03 -0800246
247 U_last = numpy.matrix(numpy.zeros((2, 1)))
248 for i in xrange(iterations):
249 X_hat = arm.X
250
251 if observer is not None:
252 observer.Y = arm.Y
253 observer.CorrectObserver(U_last)
254 self.offset_shoulder.append(observer.X_hat[4, 0])
255 self.offset_shooter.append(observer.X_hat[5, 0])
256
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800257 X_hat = observer.X_hat
258 self.x_hat_shoulder.append(observer.X_hat[0, 0])
259 self.x_hat_shooter.append(observer.X_hat[2, 0])
260
Austin Schuha88c4072016-02-06 14:31:03 -0800261 next_shoulder_goal = shoulder_profile.Update(end_goal[0, 0], end_goal[1, 0])
262 next_shooter_goal = shooter_profile.Update(end_goal[2, 0], end_goal[3, 0])
263
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800264 next_goal = numpy.concatenate(
265 (next_shoulder_goal,
266 next_shooter_goal,
267 numpy.matrix(numpy.zeros((2, 1)))),
268 axis=0)
Austin Schuha88c4072016-02-06 14:31:03 -0800269 self.goal_x_shoulder.append(goal[0, 0])
270 self.goal_v_shoulder.append(goal[1, 0])
271 self.goal_x_shooter.append(goal[2, 0])
272 self.goal_v_shooter.append(goal[3, 0])
273
274 ff_U = controller.Kff * (next_goal - observer.A * goal)
275
Austin Schuha88c4072016-02-06 14:31:03 -0800276 U_uncapped = controller.K * (goal - X_hat) + ff_U
277 U = U_uncapped.copy()
278
279 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
280 U[1, 0] = numpy.clip(U[1, 0], -vbat, vbat)
281 self.x_shoulder.append(arm.X[0, 0])
282 self.x_shooter.append(arm.X[2, 0])
283
284 if self.v_shoulder:
285 last_v_shoulder = self.v_shoulder[-1]
286 else:
287 last_v_shoulder = 0
288 self.v_shoulder.append(arm.X[1, 0])
289 self.a_shoulder.append(
290 (self.v_shoulder[-1] - last_v_shoulder) / arm.dt)
291
292 if self.v_shooter:
293 last_v_shooter = self.v_shooter[-1]
294 else:
295 last_v_shooter = 0
296 self.v_shooter.append(arm.X[3, 0])
297 self.a_shooter.append(
298 (self.v_shooter[-1] - last_v_shooter) / arm.dt)
299
300 if i % 40 == 0:
301 # Test that if we move the shoulder, the shooter stays perfect.
302 #observer.X_hat[0, 0] += 0.20
303 #arm.X[0, 0] += 0.20
304 pass
305 U_error = numpy.matrix([[0.0], [0.0]])
306 # Kick it and see what happens.
307 #if (initial_t + i * arm.dt) % 0.4 > 0.2:
308 #U_error = numpy.matrix([[4.0], [0.0]])
309 #else:
310 #U_error = numpy.matrix([[-4.0], [0.0]])
311
312 arm.Update(U + U_error)
313
314 if observer is not None:
315 observer.PredictObserver(U)
316
317 self.t.append(initial_t + i * arm.dt)
318 self.u_shoulder.append(U[0, 0])
319 self.u_shooter.append(U[1, 0])
320
321 glog.debug('Time: %f', self.t[-1])
322
323 ff_U -= U_uncapped - U
324 goal = controller.A * goal + controller.B * ff_U
325
326 if U[0, 0] != U_uncapped[0, 0]:
327 glog.debug('Moving shoulder %s', repr(initial_t + i * arm.dt))
328 glog.debug('U error %s', repr(U_uncapped - U))
329 glog.debug('goal change is %s',
330 repr(next_shoulder_goal -
331 numpy.matrix([[goal[0, 0]], [goal[1, 0]]])))
332 shoulder_profile.MoveCurrentState(
333 numpy.matrix([[goal[0, 0]], [goal[1, 0]]]))
334 if U[1, 0] != U_uncapped[1, 0]:
335 glog.debug('Moving shooter %s', repr(initial_t + i * arm.dt))
336 glog.debug('U error %s', repr(U_uncapped - U))
337 shooter_profile.MoveCurrentState(
338 numpy.matrix([[goal[2, 0]], [goal[3, 0]]]))
339 U_last = U
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800340 glog.debug('goal_error %s', repr(end_goal - goal))
341 glog.debug('error %s', repr(observer.X_hat - end_goal))
Austin Schuha88c4072016-02-06 14:31:03 -0800342
343
344 def Plot(self):
345 pylab.subplot(3, 1, 1)
346 pylab.plot(self.t, self.x_shoulder, label='x shoulder')
347 pylab.plot(self.t, self.goal_x_shoulder, label='goal x shoulder')
348 pylab.plot(self.t, self.x_hat_shoulder, label='x_hat shoulder')
349
350 pylab.plot(self.t, self.x_shooter, label='x shooter')
351 pylab.plot(self.t, self.x_hat_shooter, label='x_hat shooter')
352 pylab.plot(self.t, self.goal_x_shooter, label='goal x shooter')
353 pylab.plot(self.t, map(operator.add, self.x_shooter, self.x_shoulder),
354 label='x shooter ground')
355 pylab.plot(self.t, map(operator.add, self.x_hat_shooter, self.x_hat_shoulder),
356 label='x_hat shooter ground')
357 pylab.legend()
358
359 pylab.subplot(3, 1, 2)
360 pylab.plot(self.t, self.u_shoulder, label='u shoulder')
361 pylab.plot(self.t, self.offset_shoulder, label='voltage_offset shoulder')
362 pylab.plot(self.t, self.u_shooter, label='u shooter')
363 pylab.plot(self.t, self.offset_shooter, label='voltage_offset shooter')
364 pylab.legend()
365
366 pylab.subplot(3, 1, 3)
367 pylab.plot(self.t, self.a_shoulder, label='a_shoulder')
368 pylab.plot(self.t, self.a_shooter, label='a_shooter')
369 pylab.legend()
370
371 pylab.show()
372
373
374def main(argv):
375 argv = FLAGS(argv)
376 glog.init()
377
378 scenario_plotter = ScenarioPlotter()
379
380 arm = Arm()
381 arm_controller = IntegralArm()
382 arm_observer = IntegralArm()
383
384 # Test moving the shoulder with constant separation.
385 initial_X = numpy.matrix([[0.0], [0.0], [0.0], [0.0], [0.0], [0.0]])
386 R = numpy.matrix([[numpy.pi / 2.0],
387 [0.0],
388 [0.0], #[numpy.pi / 2.0],
389 [0.0],
390 [0.0],
391 [0.0]])
392 arm.X = initial_X[0:4, 0]
393 arm_observer.X = initial_X
394
395 scenario_plotter.run_test(arm=arm,
396 end_goal=R,
397 iterations=300,
398 controller=arm_controller,
399 observer=arm_observer)
400
401 if len(argv) != 5:
402 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
403 else:
404 namespaces = ['y2016', 'control_loops', 'superstructure']
405 loop_writer = control_loop.ControlLoopWriter('Arm', [arm],
406 namespaces=namespaces)
407 loop_writer.Write(argv[1], argv[2])
408
409 integral_loop_writer = control_loop.ControlLoopWriter(
410 'IntegralArm', [arm_controller], namespaces=namespaces)
411 integral_loop_writer.Write(argv[3], argv[4])
412
413 if FLAGS.plot:
414 scenario_plotter.Plot()
415
416if __name__ == '__main__':
417 sys.exit(main(sys.argv))