blob: ebb2f6089d9d5a4d4acc6c2020d02a20745ca50c [file] [log] [blame]
Austin Schuh82a66dc2017-03-04 15:06:44 -08001#!/usr/bin/python
2
3from aos.common.util.trapezoid_profile import TrapezoidProfile
4from frc971.control_loops.python import control_loop
5from frc971.control_loops.python import controls
6from y2017.control_loops.python import turret
7from y2017.control_loops.python import indexer
8import numpy
9import sys
10import matplotlib
11from matplotlib import pylab
12import gflags
13import glog
14
15FLAGS = gflags.FLAGS
16
17try:
18 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
19except gflags.DuplicateFlagError:
20 pass
21
22
23# TODO(austin): Shut down with no counts on the turret.
24
25class ColumnController(control_loop.ControlLoop):
26 def __init__(self, name='Column'):
27 super(ColumnController, self).__init__(name)
28 self.turret = turret.Turret(name + 'Turret')
29 self.indexer = indexer.Indexer(name + 'Indexer')
30
31 # Control loop time step
32 self.dt = 0.005
33
34 # State is [position_indexer,
35 # velocity_indexer,
36 # position_shooter,
37 # velocity_shooter]
38 # Input is [volts_indexer, volts_shooter]
39 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
40 self.B_continuous = numpy.matrix(numpy.zeros((3, 2)))
41
42 self.A_continuous[1 - 1, 1 - 1] = -(self.indexer.Kt / self.indexer.Kv / (self.indexer.J * self.indexer.resistance * self.indexer.G * self.indexer.G) +
43 self.turret.Kt / self.turret.Kv / (self.indexer.J * self.turret.resistance * self.turret.G * self.turret.G))
44 self.A_continuous[1 - 1, 3 - 1] = self.turret.Kt / self.turret.Kv / (self.indexer.J * self.turret.resistance * self.turret.G * self.turret.G)
45 self.B_continuous[1 - 1, 0] = self.indexer.Kt / (self.indexer.J * self.indexer.resistance * self.indexer.G)
46 self.B_continuous[1 - 1, 1] = -self.turret.Kt / (self.indexer.J * self.turret.resistance * self.turret.G)
47
48 self.A_continuous[2 - 1, 3 - 1] = 1
49
50 self.A_continuous[3 - 1, 1 - 1] = self.turret.Kt / self.turret.Kv / (self.turret.J * self.turret.resistance * self.turret.G * self.turret.G)
51 self.A_continuous[3 - 1, 3 - 1] = -self.turret.Kt / self.turret.Kv / (self.turret.J * self.turret.resistance * self.turret.G * self.turret.G)
52
53 self.B_continuous[3 - 1, 1] = self.turret.Kt / (self.turret.J * self.turret.resistance * self.turret.G)
54
55 self.C = numpy.matrix([[1, 0, 0], [0, 1, 0]])
56 self.D = numpy.matrix([[0, 0], [0, 0]])
57
58 self.A, self.B = self.ContinuousToDiscrete(
59 self.A_continuous, self.B_continuous, self.dt)
60
61 q_pos = 0.015
62 q_vel = 0.3
63 self.Q = numpy.matrix([[(1.0 / (q_vel ** 2.0)), 0.0, 0.0],
64 [0.0, (1.0 / (q_pos ** 2.0)), 0.0],
65 [0.0, 0.0, (1.0 / (q_vel ** 2.0))]])
66
67 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0)), 0.0],
68 [0.0, (1.0 / (12.0 ** 2.0))]])
69 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
70
71 q_vel_indexer_ff = 0.000005
72 q_pos_ff = 0.0000005
73 q_vel_ff = 0.00008
74 self.Qff = numpy.matrix([[(1.0 / (q_vel_indexer_ff ** 2.0)), 0.0, 0.0],
75 [0.0, (1.0 / (q_pos_ff ** 2.0)), 0.0],
76 [0.0, 0.0, (1.0 / (q_vel_ff ** 2.0))]])
77
78 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
79
80 self.U_max = numpy.matrix([[12.0], [12.0]])
81 self.U_min = numpy.matrix([[-12.0], [-12.0]])
82
83 self.InitializeState()
84
85
86class Column(ColumnController):
87 def __init__(self, name='Column'):
88 super(Column, self).__init__(name)
89 A_continuous = numpy.matrix(numpy.zeros((4, 4)))
90 B_continuous = numpy.matrix(numpy.zeros((4, 2)))
91
92 A_continuous[0, 1] = 1
93 A_continuous[1:, 1:] = self.A_continuous
94 B_continuous[1:, :] = self.B_continuous
95
96 self.A_continuous = A_continuous
97 self.B_continuous = B_continuous
98
99 self.A, self.B = self.ContinuousToDiscrete(
100 self.A_continuous, self.B_continuous, self.dt)
101
102 glog.debug('Eig is ' + repr(numpy.linalg.eig(self.A_continuous)))
103
104 self.C = numpy.matrix([[1, 0, 0, 0], [-1, 0, 1, 0]])
105 self.D = numpy.matrix([[0, 0], [0, 0]])
106
107 orig_K = self.K
108 self.K = numpy.matrix(numpy.zeros((2, 4)))
109 self.K[:, 1:] = orig_K
110
111 orig_Kff = self.Kff
112 self.Kff = numpy.matrix(numpy.zeros((2, 4)))
113 self.Kff[:, 1:] = orig_Kff
114
115 q_pos = 0.12
116 q_vel = 2.00
117 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0, 0.0],
118 [0.0, (q_vel ** 2.0), 0.0, 0.0],
119 [0.0, 0.0, (q_pos ** 2.0), 0.0],
120 [0.0, 0.0, 0.0, (q_vel ** 2.0)]])
121
122 r_pos = 0.05
123 self.R = numpy.matrix([[(r_pos ** 2.0), 0.0],
124 [0.0, (r_pos ** 2.0)]])
125
126 self.KalmanGain, self.Q_steady = controls.kalman(
127 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
128 self.L = self.A * self.KalmanGain
129
130 self.InitializeState()
131
132
133class IntegralColumn(Column):
134 def __init__(self, name='IntegralColumn', voltage_error_noise=None):
135 super(IntegralColumn, self).__init__(name)
136
137 A_continuous = numpy.matrix(numpy.zeros((6, 6)))
138 A_continuous[0:4, 0:4] = self.A_continuous
139 A_continuous[0:4:, 4:6] = self.B_continuous
140
141 B_continuous = numpy.matrix(numpy.zeros((6, 2)))
142 B_continuous[0:4, :] = self.B_continuous
143
144 self.A_continuous = A_continuous
145 self.B_continuous = B_continuous
146 glog.debug('A_continuous: ' + repr(self.A_continuous))
147 glog.debug('B_continuous: ' + repr(self.B_continuous))
148
149 self.A, self.B = self.ContinuousToDiscrete(
150 self.A_continuous, self.B_continuous, self.dt)
151
152 glog.debug('Eig is ' + repr(numpy.linalg.eig(self.A_continuous)))
153
154 C = numpy.matrix(numpy.zeros((2, 6)))
155 C[0:2, 0:4] = self.C
156 self.C = C
157 glog.debug('C is ' + repr(self.C))
158
159 self.D = numpy.matrix([[0, 0], [0, 0]])
160
161 orig_K = self.K
162 self.K = numpy.matrix(numpy.zeros((2, 6)))
163 self.K[:, 0:4] = orig_K
164 self.K[0, 4] = 1
165 self.K[1, 5] = 1
166
167 orig_Kff = self.Kff
168 self.Kff = numpy.matrix(numpy.zeros((2, 6)))
169 self.Kff[:, 0:4] = orig_Kff
170
171 q_pos = 0.12
172 q_vel = 2.00
173 q_voltage = 4.0
174 if voltage_error_noise is not None:
175 q_voltage = voltage_error_noise
176
177 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0, 0.0, 0.0, 0.0],
178 [0.0, (q_vel ** 2.0), 0.0, 0.0, 0.0, 0.0],
179 [0.0, 0.0, (q_pos ** 2.0), 0.0, 0.0, 0.0],
180 [0.0, 0.0, 0.0, (q_vel ** 2.0), 0.0, 0.0],
181 [0.0, 0.0, 0.0, 0.0, (q_voltage ** 2.0), 0.0],
182 [0.0, 0.0, 0.0, 0.0, 0.0, (q_voltage ** 2.0)]])
183
184 r_pos = 0.05
185 self.R = numpy.matrix([[(r_pos ** 2.0), 0.0],
186 [0.0, (r_pos ** 2.0)]])
187
188 self.KalmanGain, self.Q_steady = controls.kalman(
189 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
190 self.L = self.A * self.KalmanGain
191
192 self.InitializeState()
193
194
195class ScenarioPlotter(object):
196 def __init__(self):
197 # Various lists for graphing things.
198 self.t = []
199 self.xi = []
200 self.xt = []
201 self.vi = []
202 self.vt = []
203 self.ai = []
204 self.at = []
205 self.x_hat = []
206 self.ui = []
207 self.ut = []
208 self.ui_fb = []
209 self.ut_fb = []
210 self.offseti = []
211 self.offsett = []
212 self.turret_error = []
213
214 def run_test(self, column, end_goal,
215 controller_column,
216 observer_column=None,
217 iterations=200):
218 """Runs the column plant with an initial condition and goal.
219
220 Args:
221 column: column object to use.
222 end_goal: end_goal state.
223 controller_column: Intake object to get K from, or None if we should
224 use column.
225 observer_column: Intake object to use for the observer, or None if we should
226 use the actual state.
227 iterations: Number of timesteps to run the model for.
228 """
229
230 if controller_column is None:
231 controller_column = column
232
233 vbat = 12.0
234
235 if self.t:
236 initial_t = self.t[-1] + column.dt
237 else:
238 initial_t = 0
239
240 goal = numpy.concatenate((column.X, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
241
242 profile = TrapezoidProfile(column.dt)
243 profile.set_maximum_acceleration(10.0)
244 profile.set_maximum_velocity(3.0)
245 profile.SetGoal(goal[2, 0])
246
247 U_last = numpy.matrix(numpy.zeros((2, 1)))
248 for i in xrange(iterations):
249 observer_column.Y = column.Y
250 observer_column.CorrectObserver(U_last)
251
252 self.offseti.append(observer_column.X_hat[4, 0])
253 self.offsett.append(observer_column.X_hat[5, 0])
254 self.x_hat.append(observer_column.X_hat[0, 0])
255
256 next_goal = numpy.concatenate(
257 (end_goal[0:2, :],
258 profile.Update(end_goal[2, 0], end_goal[3, 0]),
259 end_goal[4:6, :]),
260 axis=0)
261
262 ff_U = controller_column.Kff * (next_goal - observer_column.A * goal)
263 fb_U = controller_column.K * (goal - observer_column.X_hat)
264 self.turret_error.append((goal[2, 0] - column.X[2, 0]) * 100.0)
265 self.ui_fb.append(fb_U[0, 0])
266 self.ut_fb.append(fb_U[1, 0])
267
268 U_uncapped = ff_U + fb_U
269
270 U = U_uncapped.copy()
271 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
272 U[1, 0] = numpy.clip(U[1, 0], -vbat, vbat)
273 self.xi.append(column.X[0, 0])
274 self.xt.append(column.X[2, 0])
275
276 if self.vi:
277 last_vi = self.vi[-1]
278 else:
279 last_vi = 0
280 if self.vt:
281 last_vt = self.vt[-1]
282 else:
283 last_vt = 0
284
285 self.vi.append(column.X[1, 0])
286 self.vt.append(column.X[3, 0])
287 self.ai.append((self.vi[-1] - last_vi) / column.dt)
288 self.at.append((self.vt[-1] - last_vt) / column.dt)
289
290 offset = 0.0
291 if i > 100:
292 offset = 1.0
293 column.Update(U + numpy.matrix([[offset], [0.0]]))
294
295 observer_column.PredictObserver(U)
296
297 self.t.append(initial_t + i * column.dt)
298 self.ui.append(U[0, 0])
299 self.ut.append(U[1, 0])
300
301 ff_U -= U_uncapped - U
302 goal = controller_column.A * goal + controller_column.B * ff_U
303
304 if U[1, 0] != U_uncapped[1, 0]:
305 profile.MoveCurrentState(
306 numpy.matrix([[goal[2, 0]], [goal[3, 0]]]))
307
308 glog.debug('Time: %f', self.t[-1])
309 glog.debug('goal_error %s', repr(end_goal - goal))
310 glog.debug('error %s', repr(observer_column.X_hat - end_goal))
311
312 def Plot(self):
313 pylab.subplot(3, 1, 1)
314 pylab.plot(self.t, self.xi, label='xi')
315 pylab.plot(self.t, self.xt, label='xt')
316 pylab.plot(self.t, self.x_hat, label='x_hat')
317 pylab.plot(self.t, self.turret_error, label='turret_error')
318 pylab.legend()
319
320 pylab.subplot(3, 1, 2)
321 pylab.plot(self.t, self.ui, label='ui')
322 pylab.plot(self.t, self.ui_fb, label='ui_fb')
323 pylab.plot(self.t, self.ut, label='ut')
324 pylab.plot(self.t, self.ut_fb, label='ut_fb')
325 pylab.plot(self.t, self.offseti, label='voltage_offseti')
326 pylab.plot(self.t, self.offsett, label='voltage_offsett')
327 pylab.legend()
328
329 pylab.subplot(3, 1, 3)
330 pylab.plot(self.t, self.ai, label='ai')
331 pylab.plot(self.t, self.at, label='at')
332 pylab.plot(self.t, self.vi, label='vi')
333 pylab.plot(self.t, self.vt, label='vt')
334 pylab.legend()
335
336 pylab.show()
337
338
339def main(argv):
340 scenario_plotter = ScenarioPlotter()
341
342 column = Column()
343 column_controller = IntegralColumn()
344 observer_column = IntegralColumn()
345
346 initial_X = numpy.matrix([[0.0], [0.0], [0.0], [0.0]])
347 R = numpy.matrix([[0.0], [10.0], [5.0], [0.0], [0.0], [0.0]])
348 scenario_plotter.run_test(column, end_goal=R, controller_column=column_controller,
349 observer_column=observer_column, iterations=600)
350
351 if FLAGS.plot:
352 scenario_plotter.Plot()
353
354 if len(argv) != 7:
355 glog.fatal('Expected .h file name and .cc file names')
356 else:
357 namespaces = ['y2017', 'control_loops', 'superstructure', 'column']
358 column = Column('Column')
359 loop_writer = control_loop.ControlLoopWriter('Column', [column],
360 namespaces=namespaces)
361 loop_writer.Write(argv[1], argv[2])
362
363 integral_column = IntegralColumn('IntegralColumn')
364 integral_loop_writer = control_loop.ControlLoopWriter(
365 'IntegralColumn', [integral_column], namespaces=namespaces)
366 integral_loop_writer.Write(argv[3], argv[4])
367
368 stuck_integral_column = IntegralColumn('StuckIntegralColumn', voltage_error_noise=8.0)
369 stuck_integral_loop_writer = control_loop.ControlLoopWriter(
370 'StuckIntegralColumn', [stuck_integral_column], namespaces=namespaces)
371 stuck_integral_loop_writer.Write(argv[5], argv[6])
372
373
374if __name__ == '__main__':
375 argv = FLAGS(sys.argv)
376 glog.init()
377 sys.exit(main(argv))