blob: d6043b3c8b743b8d732f9f58b0b71344e2db9241 [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
Austin Schuh82a66dc2017-03-04 15:06:44 -080023class ColumnController(control_loop.ControlLoop):
24 def __init__(self, name='Column'):
25 super(ColumnController, self).__init__(name)
26 self.turret = turret.Turret(name + 'Turret')
27 self.indexer = indexer.Indexer(name + 'Indexer')
28
29 # Control loop time step
30 self.dt = 0.005
31
32 # State is [position_indexer,
33 # velocity_indexer,
34 # position_shooter,
35 # velocity_shooter]
36 # Input is [volts_indexer, volts_shooter]
37 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
38 self.B_continuous = numpy.matrix(numpy.zeros((3, 2)))
39
Austin Schuhd5ccb862017-03-11 22:06:36 -080040 self.A_continuous[0, 0] = -(self.indexer.Kt / self.indexer.Kv / (self.indexer.J * self.indexer.resistance * self.indexer.G * self.indexer.G) +
Austin Schuh82a66dc2017-03-04 15:06:44 -080041 self.turret.Kt / self.turret.Kv / (self.indexer.J * self.turret.resistance * self.turret.G * self.turret.G))
Austin Schuhd5ccb862017-03-11 22:06:36 -080042 self.A_continuous[0, 2] = self.turret.Kt / self.turret.Kv / (self.indexer.J * self.turret.resistance * self.turret.G * self.turret.G)
43 self.B_continuous[0, 0] = self.indexer.Kt / (self.indexer.J * self.indexer.resistance * self.indexer.G)
44 self.B_continuous[0, 1] = -self.turret.Kt / (self.indexer.J * self.turret.resistance * self.turret.G)
Austin Schuh82a66dc2017-03-04 15:06:44 -080045
Austin Schuhd5ccb862017-03-11 22:06:36 -080046 self.A_continuous[1, 2] = 1
Austin Schuh82a66dc2017-03-04 15:06:44 -080047
Austin Schuhd5ccb862017-03-11 22:06:36 -080048 self.A_continuous[2, 0] = self.turret.Kt / self.turret.Kv / (self.turret.J * self.turret.resistance * self.turret.G * self.turret.G)
49 self.A_continuous[2, 2] = -self.turret.Kt / self.turret.Kv / (self.turret.J * self.turret.resistance * self.turret.G * self.turret.G)
Austin Schuh82a66dc2017-03-04 15:06:44 -080050
Austin Schuhd5ccb862017-03-11 22:06:36 -080051 self.B_continuous[2, 1] = self.turret.Kt / (self.turret.J * self.turret.resistance * self.turret.G)
Austin Schuh82a66dc2017-03-04 15:06:44 -080052
53 self.C = numpy.matrix([[1, 0, 0], [0, 1, 0]])
54 self.D = numpy.matrix([[0, 0], [0, 0]])
55
56 self.A, self.B = self.ContinuousToDiscrete(
57 self.A_continuous, self.B_continuous, self.dt)
58
Austin Schuhd5ccb862017-03-11 22:06:36 -080059 q_indexer_vel = 13.0
Austin Schuheb5c22e2017-04-09 18:30:28 -070060 q_pos = 0.05
Austin Schuhd5ccb862017-03-11 22:06:36 -080061 q_vel = 0.8
62 self.Q = numpy.matrix([[(1.0 / (q_indexer_vel ** 2.0)), 0.0, 0.0],
Austin Schuh82a66dc2017-03-04 15:06:44 -080063 [0.0, (1.0 / (q_pos ** 2.0)), 0.0],
64 [0.0, 0.0, (1.0 / (q_vel ** 2.0))]])
65
66 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0)), 0.0],
67 [0.0, (1.0 / (12.0 ** 2.0))]])
68 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
69
Austin Schuhd5ccb862017-03-11 22:06:36 -080070 glog.debug('Controller poles are ' + repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
71
Austin Schuh82a66dc2017-03-04 15:06:44 -080072 q_vel_indexer_ff = 0.000005
73 q_pos_ff = 0.0000005
74 q_vel_ff = 0.00008
75 self.Qff = numpy.matrix([[(1.0 / (q_vel_indexer_ff ** 2.0)), 0.0, 0.0],
76 [0.0, (1.0 / (q_pos_ff ** 2.0)), 0.0],
77 [0.0, 0.0, (1.0 / (q_vel_ff ** 2.0))]])
78
79 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
80
81 self.U_max = numpy.matrix([[12.0], [12.0]])
82 self.U_min = numpy.matrix([[-12.0], [-12.0]])
83
84 self.InitializeState()
85
86
87class Column(ColumnController):
Austin Schuhd5ccb862017-03-11 22:06:36 -080088 def __init__(self, name='Column', disable_indexer=False):
Austin Schuh82a66dc2017-03-04 15:06:44 -080089 super(Column, self).__init__(name)
90 A_continuous = numpy.matrix(numpy.zeros((4, 4)))
91 B_continuous = numpy.matrix(numpy.zeros((4, 2)))
92
93 A_continuous[0, 1] = 1
94 A_continuous[1:, 1:] = self.A_continuous
95 B_continuous[1:, :] = self.B_continuous
96
97 self.A_continuous = A_continuous
98 self.B_continuous = B_continuous
99
100 self.A, self.B = self.ContinuousToDiscrete(
101 self.A_continuous, self.B_continuous, self.dt)
102
Austin Schuh82a66dc2017-03-04 15:06:44 -0800103 self.C = numpy.matrix([[1, 0, 0, 0], [-1, 0, 1, 0]])
104 self.D = numpy.matrix([[0, 0], [0, 0]])
105
106 orig_K = self.K
107 self.K = numpy.matrix(numpy.zeros((2, 4)))
108 self.K[:, 1:] = orig_K
109
Austin Schuhd5ccb862017-03-11 22:06:36 -0800110 glog.debug('K is ' + repr(self.K))
111 # TODO(austin): Do we want to damp velocity out or not when disabled?
112 #if disable_indexer:
113 # self.K[0, 1] = 0.0
114 # self.K[1, 1] = 0.0
115
Austin Schuh82a66dc2017-03-04 15:06:44 -0800116 orig_Kff = self.Kff
117 self.Kff = numpy.matrix(numpy.zeros((2, 4)))
118 self.Kff[:, 1:] = orig_Kff
119
120 q_pos = 0.12
121 q_vel = 2.00
122 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0, 0.0],
123 [0.0, (q_vel ** 2.0), 0.0, 0.0],
124 [0.0, 0.0, (q_pos ** 2.0), 0.0],
125 [0.0, 0.0, 0.0, (q_vel ** 2.0)]])
126
127 r_pos = 0.05
128 self.R = numpy.matrix([[(r_pos ** 2.0), 0.0],
129 [0.0, (r_pos ** 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.InitializeState()
136
137
138class IntegralColumn(Column):
Austin Schuhd5ccb862017-03-11 22:06:36 -0800139 def __init__(self, name='IntegralColumn', voltage_error_noise=None,
140 disable_indexer=False):
Austin Schuh82a66dc2017-03-04 15:06:44 -0800141 super(IntegralColumn, self).__init__(name)
142
143 A_continuous = numpy.matrix(numpy.zeros((6, 6)))
144 A_continuous[0:4, 0:4] = self.A_continuous
145 A_continuous[0:4:, 4:6] = self.B_continuous
146
147 B_continuous = numpy.matrix(numpy.zeros((6, 2)))
148 B_continuous[0:4, :] = self.B_continuous
149
150 self.A_continuous = A_continuous
151 self.B_continuous = B_continuous
Austin Schuh82a66dc2017-03-04 15:06:44 -0800152
153 self.A, self.B = self.ContinuousToDiscrete(
154 self.A_continuous, self.B_continuous, self.dt)
155
Austin Schuh82a66dc2017-03-04 15:06:44 -0800156 C = numpy.matrix(numpy.zeros((2, 6)))
157 C[0:2, 0:4] = self.C
158 self.C = C
Austin Schuh82a66dc2017-03-04 15:06:44 -0800159
160 self.D = numpy.matrix([[0, 0], [0, 0]])
161
162 orig_K = self.K
163 self.K = numpy.matrix(numpy.zeros((2, 6)))
164 self.K[:, 0:4] = orig_K
Austin Schuhd5ccb862017-03-11 22:06:36 -0800165
166 # TODO(austin): I'm not certain this is ideal. If someone spins the bottom
167 # at a constant rate, we'll learn a voltage offset. That should translate
168 # directly to a voltage on the turret to hold it steady. I'm also not
169 # convinced we care that much. If the indexer is off, it'll stop rather
170 # quickly anyways, so this is mostly a moot point.
171 if not disable_indexer:
172 self.K[0, 4] = 1
Austin Schuh82a66dc2017-03-04 15:06:44 -0800173 self.K[1, 5] = 1
174
175 orig_Kff = self.Kff
176 self.Kff = numpy.matrix(numpy.zeros((2, 6)))
177 self.Kff[:, 0:4] = orig_Kff
178
Austin Schuheb5c22e2017-04-09 18:30:28 -0700179 q_pos = 0.40
Austin Schuh82a66dc2017-03-04 15:06:44 -0800180 q_vel = 2.00
Austin Schuheb5c22e2017-04-09 18:30:28 -0700181 q_voltage = 8.0
Austin Schuh82a66dc2017-03-04 15:06:44 -0800182 if voltage_error_noise is not None:
183 q_voltage = voltage_error_noise
184
185 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0, 0.0, 0.0, 0.0],
186 [0.0, (q_vel ** 2.0), 0.0, 0.0, 0.0, 0.0],
187 [0.0, 0.0, (q_pos ** 2.0), 0.0, 0.0, 0.0],
188 [0.0, 0.0, 0.0, (q_vel ** 2.0), 0.0, 0.0],
189 [0.0, 0.0, 0.0, 0.0, (q_voltage ** 2.0), 0.0],
190 [0.0, 0.0, 0.0, 0.0, 0.0, (q_voltage ** 2.0)]])
191
192 r_pos = 0.05
193 self.R = numpy.matrix([[(r_pos ** 2.0), 0.0],
194 [0.0, (r_pos ** 2.0)]])
195
196 self.KalmanGain, self.Q_steady = controls.kalman(
197 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
198 self.L = self.A * self.KalmanGain
199
200 self.InitializeState()
201
202
203class ScenarioPlotter(object):
204 def __init__(self):
205 # Various lists for graphing things.
206 self.t = []
207 self.xi = []
208 self.xt = []
209 self.vi = []
210 self.vt = []
211 self.ai = []
212 self.at = []
213 self.x_hat = []
214 self.ui = []
215 self.ut = []
216 self.ui_fb = []
217 self.ut_fb = []
218 self.offseti = []
219 self.offsett = []
220 self.turret_error = []
221
222 def run_test(self, column, end_goal,
223 controller_column,
224 observer_column=None,
225 iterations=200):
226 """Runs the column plant with an initial condition and goal.
227
228 Args:
229 column: column object to use.
230 end_goal: end_goal state.
231 controller_column: Intake object to get K from, or None if we should
232 use column.
233 observer_column: Intake object to use for the observer, or None if we should
234 use the actual state.
235 iterations: Number of timesteps to run the model for.
236 """
237
238 if controller_column is None:
239 controller_column = column
240
241 vbat = 12.0
242
243 if self.t:
244 initial_t = self.t[-1] + column.dt
245 else:
246 initial_t = 0
247
248 goal = numpy.concatenate((column.X, numpy.matrix(numpy.zeros((2, 1)))), axis=0)
249
250 profile = TrapezoidProfile(column.dt)
251 profile.set_maximum_acceleration(10.0)
252 profile.set_maximum_velocity(3.0)
253 profile.SetGoal(goal[2, 0])
254
255 U_last = numpy.matrix(numpy.zeros((2, 1)))
256 for i in xrange(iterations):
257 observer_column.Y = column.Y
258 observer_column.CorrectObserver(U_last)
259
260 self.offseti.append(observer_column.X_hat[4, 0])
261 self.offsett.append(observer_column.X_hat[5, 0])
262 self.x_hat.append(observer_column.X_hat[0, 0])
263
264 next_goal = numpy.concatenate(
265 (end_goal[0:2, :],
266 profile.Update(end_goal[2, 0], end_goal[3, 0]),
267 end_goal[4:6, :]),
268 axis=0)
269
270 ff_U = controller_column.Kff * (next_goal - observer_column.A * goal)
271 fb_U = controller_column.K * (goal - observer_column.X_hat)
272 self.turret_error.append((goal[2, 0] - column.X[2, 0]) * 100.0)
273 self.ui_fb.append(fb_U[0, 0])
274 self.ut_fb.append(fb_U[1, 0])
275
276 U_uncapped = ff_U + fb_U
277
278 U = U_uncapped.copy()
279 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
280 U[1, 0] = numpy.clip(U[1, 0], -vbat, vbat)
281 self.xi.append(column.X[0, 0])
282 self.xt.append(column.X[2, 0])
283
284 if self.vi:
285 last_vi = self.vi[-1]
286 else:
287 last_vi = 0
288 if self.vt:
289 last_vt = self.vt[-1]
290 else:
291 last_vt = 0
292
293 self.vi.append(column.X[1, 0])
294 self.vt.append(column.X[3, 0])
295 self.ai.append((self.vi[-1] - last_vi) / column.dt)
296 self.at.append((self.vt[-1] - last_vt) / column.dt)
297
298 offset = 0.0
299 if i > 100:
300 offset = 1.0
Austin Schuhd5ccb862017-03-11 22:06:36 -0800301 column.Update(U + numpy.matrix([[0.0], [offset]]))
Austin Schuh82a66dc2017-03-04 15:06:44 -0800302
303 observer_column.PredictObserver(U)
304
305 self.t.append(initial_t + i * column.dt)
306 self.ui.append(U[0, 0])
307 self.ut.append(U[1, 0])
308
309 ff_U -= U_uncapped - U
310 goal = controller_column.A * goal + controller_column.B * ff_U
311
312 if U[1, 0] != U_uncapped[1, 0]:
313 profile.MoveCurrentState(
314 numpy.matrix([[goal[2, 0]], [goal[3, 0]]]))
315
316 glog.debug('Time: %f', self.t[-1])
Austin Schuhd5ccb862017-03-11 22:06:36 -0800317 glog.debug('goal_error %s', repr((end_goal - goal).T))
318 glog.debug('error %s', repr((observer_column.X_hat - end_goal).T))
Austin Schuh82a66dc2017-03-04 15:06:44 -0800319
320 def Plot(self):
321 pylab.subplot(3, 1, 1)
Austin Schuhd5ccb862017-03-11 22:06:36 -0800322 pylab.plot(self.t, self.xi, label='x_indexer')
323 pylab.plot(self.t, self.xt, label='x_turret')
Austin Schuh82a66dc2017-03-04 15:06:44 -0800324 pylab.plot(self.t, self.x_hat, label='x_hat')
Austin Schuhd5ccb862017-03-11 22:06:36 -0800325 pylab.plot(self.t, self.turret_error, label='turret_error * 100')
Austin Schuh82a66dc2017-03-04 15:06:44 -0800326 pylab.legend()
327
328 pylab.subplot(3, 1, 2)
Austin Schuhd5ccb862017-03-11 22:06:36 -0800329 pylab.plot(self.t, self.ui, label='u_indexer')
330 pylab.plot(self.t, self.ui_fb, label='u_indexer_fb')
331 pylab.plot(self.t, self.ut, label='u_turret')
332 pylab.plot(self.t, self.ut_fb, label='u_turret_fb')
333 pylab.plot(self.t, self.offseti, label='voltage_offset_indexer')
334 pylab.plot(self.t, self.offsett, label='voltage_offset_turret')
Austin Schuh82a66dc2017-03-04 15:06:44 -0800335 pylab.legend()
336
337 pylab.subplot(3, 1, 3)
Austin Schuhd5ccb862017-03-11 22:06:36 -0800338 pylab.plot(self.t, self.ai, label='a_indexer')
339 pylab.plot(self.t, self.at, label='a_turret')
340 pylab.plot(self.t, self.vi, label='v_indexer')
341 pylab.plot(self.t, self.vt, label='v_turret')
Austin Schuh82a66dc2017-03-04 15:06:44 -0800342 pylab.legend()
343
344 pylab.show()
345
346
347def main(argv):
348 scenario_plotter = ScenarioPlotter()
349
350 column = Column()
351 column_controller = IntegralColumn()
352 observer_column = IntegralColumn()
353
354 initial_X = numpy.matrix([[0.0], [0.0], [0.0], [0.0]])
355 R = numpy.matrix([[0.0], [10.0], [5.0], [0.0], [0.0], [0.0]])
356 scenario_plotter.run_test(column, end_goal=R, controller_column=column_controller,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800357 observer_column=observer_column, iterations=400)
Austin Schuh82a66dc2017-03-04 15:06:44 -0800358
359 if FLAGS.plot:
360 scenario_plotter.Plot()
361
362 if len(argv) != 7:
363 glog.fatal('Expected .h file name and .cc file names')
364 else:
365 namespaces = ['y2017', 'control_loops', 'superstructure', 'column']
366 column = Column('Column')
367 loop_writer = control_loop.ControlLoopWriter('Column', [column],
368 namespaces=namespaces)
Austin Schuhd5ccb862017-03-11 22:06:36 -0800369 loop_writer.AddConstant(control_loop.Constant(
370 'kIndexerFreeSpeed', '%f', column.indexer.free_speed))
371 loop_writer.AddConstant(control_loop.Constant(
372 'kIndexerOutputRatio', '%f', column.indexer.G))
373 loop_writer.AddConstant(control_loop.Constant(
374 'kTurretFreeSpeed', '%f', column.turret.free_speed))
375 loop_writer.AddConstant(control_loop.Constant(
376 'kTurretOutputRatio', '%f', column.turret.G))
Austin Schuh82a66dc2017-03-04 15:06:44 -0800377 loop_writer.Write(argv[1], argv[2])
378
Austin Schuhd5ccb862017-03-11 22:06:36 -0800379 # IntegralColumn controller 1 will disable the indexer.
Austin Schuh82a66dc2017-03-04 15:06:44 -0800380 integral_column = IntegralColumn('IntegralColumn')
Austin Schuhd5ccb862017-03-11 22:06:36 -0800381 disabled_integral_column = IntegralColumn('DisabledIntegralColumn',
382 disable_indexer=True)
Austin Schuh82a66dc2017-03-04 15:06:44 -0800383 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800384 'IntegralColumn', [integral_column, disabled_integral_column],
385 namespaces=namespaces)
Austin Schuh82a66dc2017-03-04 15:06:44 -0800386 integral_loop_writer.Write(argv[3], argv[4])
387
388 stuck_integral_column = IntegralColumn('StuckIntegralColumn', voltage_error_noise=8.0)
389 stuck_integral_loop_writer = control_loop.ControlLoopWriter(
390 'StuckIntegralColumn', [stuck_integral_column], namespaces=namespaces)
391 stuck_integral_loop_writer.Write(argv[5], argv[6])
392
393
394if __name__ == '__main__':
395 argv = FLAGS(sys.argv)
396 glog.init()
397 sys.exit(main(argv))