blob: 2606c445cb6dab08afad958fc276152ef062a1f0 [file] [log] [blame]
Austin Schuh82162452022-02-07 22:01:45 -08001from frc971.control_loops.python import control_loop
2from frc971.control_loops.python import controls
3import numpy
4from matplotlib import pylab
5
6import gflags
7import glog
8
9
10class CatapultParams(object):
11 def __init__(self,
12 name,
13 motor,
14 G,
15 J,
16 lever,
17 q_pos,
18 q_vel,
19 q_voltage,
20 r_pos,
21 controller_poles,
22 dt=0.00505):
23 self.name = name
24 self.motor = motor
25 self.G = G
26 self.J = J
27 self.lever = lever
28 self.q_pos = q_pos
29 self.q_vel = q_vel
30 self.q_voltage = q_voltage
31 self.r_pos = r_pos
32 self.dt = dt
33 self.controller_poles = controller_poles
34
35
36class VelocityCatapult(control_loop.HybridControlLoop):
37 def __init__(self, params, name="Catapult"):
38 super(VelocityCatapult, self).__init__(name=name)
39 self.params = params
40 # Set Motor
41 self.motor = self.params.motor
42 # Gear ratio
43 self.G = self.params.G
44 # Moment of inertia of the catapult wheel in kg m^2
45 self.J = self.params.J + self.motor.motor_inertia / (self.G**2.0)
46 # Control loop time step
47 self.dt = self.params.dt
48
49 # State feedback matrices
50 # [angular velocity]
51 self.A_continuous = numpy.matrix([[
52 -self.motor.Kt / self.motor.Kv /
53 (self.J * self.G * self.G * self.motor.resistance)
54 ]])
55 self.B_continuous = numpy.matrix(
56 [[self.motor.Kt / (self.J * self.G * self.motor.resistance)]])
57 self.C = numpy.matrix([[1]])
58 self.D = numpy.matrix([[0]])
59
60 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
61 self.B_continuous, self.dt)
62
63 self.PlaceControllerPoles(self.params.controller_poles)
64
65 # Generated controller not used.
66 self.PlaceObserverPoles([0.3])
67
68 self.U_max = numpy.matrix([[12.0]])
69 self.U_min = numpy.matrix([[-12.0]])
70
71 qff_vel = 8.0
72 self.Qff = numpy.matrix([[1.0 / (qff_vel**2.0)]])
73
74 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
75
76 glog.debug('K: %s', str(self.K))
77 glog.debug('Poles: %s',
78 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
79
80
81class Catapult(VelocityCatapult):
82 def __init__(self, params, name="Catapult"):
83 super(Catapult, self).__init__(params, name=name)
84
85 self.A_continuous_unaugmented = self.A_continuous
86 self.B_continuous_unaugmented = self.B_continuous
87
88 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
89 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
90 self.A_continuous[0, 1] = 1
91
92 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
93 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
94
95 # State feedback matrices
96 # [position, angular velocity]
97 self.C = numpy.matrix([[1, 0]])
98 self.D = numpy.matrix([[0]])
99
100 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
101 self.B_continuous, self.dt)
102
103 rpl = 0.45
104 ipl = 0.07
105 self.PlaceObserverPoles([rpl + 1j * ipl, rpl - 1j * ipl])
106
107 self.K_unaugmented = self.K
108 self.K = numpy.matrix(numpy.zeros((1, 2)))
109 self.K[0, 1:2] = self.K_unaugmented
110 self.Kff_unaugmented = self.Kff
111 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
112 self.Kff[0, 1:2] = self.Kff_unaugmented
113
114 self.InitializeState()
115
116
117class IntegralCatapult(Catapult):
118 def __init__(self, params, name="IntegralCatapult"):
119 super(IntegralCatapult, self).__init__(params, name=name)
120
121 self.A_continuous_unaugmented = self.A_continuous
122 self.B_continuous_unaugmented = self.B_continuous
123
124 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
125 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
126 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
127
128 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
129 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
130
131 # states
132 # [position, velocity, voltage_error]
133 self.C_unaugmented = self.C
134 self.C = numpy.matrix(numpy.zeros((1, 3)))
135 self.C[0:1, 0:2] = self.C_unaugmented
136
137 glog.debug('A_continuous %s' % str(self.A_continuous))
138 glog.debug('B_continuous %s' % str(self.B_continuous))
139 glog.debug('C %s' % str(self.C))
140
141 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
142 self.B_continuous, self.dt)
143
144 glog.debug('A %s' % str(self.A))
145 glog.debug('B %s' % str(self.B))
146
147 q_pos = self.params.q_pos
148 q_vel = self.params.q_vel
149 q_voltage = self.params.q_voltage
150 self.Q_continuous = numpy.matrix([[(q_pos**2.0), 0.0,
151 0.0], [0.0, (q_vel**2.0), 0.0],
152 [0.0, 0.0, (q_voltage**2.0)]])
153
154 r_pos = self.params.r_pos
155 self.R_continuous = numpy.matrix([[(r_pos**2.0)]])
156
157 _, _, self.Q, self.R = controls.kalmd(
158 A_continuous=self.A_continuous,
159 B_continuous=self.B_continuous,
160 Q_continuous=self.Q_continuous,
161 R_continuous=self.R_continuous,
162 dt=self.dt)
163
164 glog.debug('Q_discrete %s' % (str(self.Q)))
165 glog.debug('R_discrete %s' % (str(self.R)))
166
167 self.KalmanGain, self.P_steady_state = controls.kalman(
168 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
169 self.L = self.A * self.KalmanGain
170
171 self.K_unaugmented = self.K
172 self.K = numpy.matrix(numpy.zeros((1, 3)))
173 self.K[0, 0:2] = self.K_unaugmented
174 self.K[0, 2] = 1
175 self.Kff_unaugmented = self.Kff
176 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
177 self.Kff[0, 0:2] = self.Kff_unaugmented
178
179 self.InitializeState()
180
181
182def MaxSpeed(params, U, final_position):
183 """Runs the catapult plant with an initial condition and goal.
184
185 Args:
186 catapult: Catapult object to use.
187 goal: goal state.
188 iterations: Number of timesteps to run the model for.
189 controller_catapult: Catapult object to get K from, or None if we should
190 use catapult.
191 observer_catapult: Catapult object to use for the observer, or None if we
192 should use the actual state.
193 """
194
195 # Various lists for graphing things.
196 catapult = Catapult(params, params.name)
197 controller_catapult = IntegralCatapult(params, params.name)
198 observer_catapult = IntegralCatapult(params, params.name)
199 vbat = 12.0
200
201 while True:
202 X_hat = catapult.X
203 if catapult.X[0, 0] > final_position:
204 return catapult.X[1, 0] * params.lever
205
206 if observer_catapult is not None:
207 X_hat = observer_catapult.X_hat
208
209 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
210
211 if observer_catapult is not None:
212 observer_catapult.Y = catapult.Y
213 observer_catapult.CorrectHybridObserver(U)
214
215 applied_U = U.copy()
216 catapult.Update(applied_U)
217
218 if observer_catapult is not None:
219 observer_catapult.PredictHybridObserver(U, catapult.dt)
220
221
222def PlotShot(params, U, final_position):
223 """Runs the catapult plant with an initial condition and goal.
224
225 Args:
226 catapult: Catapult object to use.
227 goal: goal state.
228 iterations: Number of timesteps to run the model for.
229 controller_catapult: Catapult object to get K from, or None if we should
230 use catapult.
231 observer_catapult: Catapult object to use for the observer, or None if we
232 should use the actual state.
233 """
234
235 # Various lists for graphing things.
236 t = []
237 x = []
238 x_hat = []
239 v = []
240 w_hat = []
241 v_hat = []
242 a = []
243 u = []
244 offset = []
245
246 catapult = Catapult(params, params.name)
247 controller_catapult = IntegralCatapult(params, params.name)
248 observer_catapult = IntegralCatapult(params, params.name)
249 vbat = 12.0
250
251 if t:
252 initial_t = t[-1] + catapult.dt
253 else:
254 initial_t = 0
255
256 for i in range(10000):
257 X_hat = catapult.X
258 if catapult.X[0, 0] > final_position:
259 break
260
261 if observer_catapult is not None:
262 X_hat = observer_catapult.X_hat
263 x_hat.append(observer_catapult.X_hat[0, 0])
264 w_hat.append(observer_catapult.X_hat[1, 0])
265 v_hat.append(observer_catapult.X_hat[1, 0] * params.lever)
266
267 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
268 x.append(catapult.X[0, 0])
269
270 if v:
271 last_v = v[-1]
272 else:
273 last_v = 0
274
275 v.append(catapult.X[1, 0])
276 a.append((v[-1] - last_v) / catapult.dt)
277
278 if observer_catapult is not None:
279 observer_catapult.Y = catapult.Y
280 observer_catapult.CorrectHybridObserver(U)
281 offset.append(observer_catapult.X_hat[2, 0])
282
283 catapult.Update(U)
284
285 if observer_catapult is not None:
286 observer_catapult.PredictHybridObserver(U, catapult.dt)
287
288 t.append(initial_t + i * catapult.dt)
289 u.append(U[0, 0])
290
291 pylab.subplot(3, 1, 1)
292 pylab.plot(t, v, label='v')
293 pylab.plot(t, x_hat, label='x_hat')
294 pylab.plot(t, v, label='v')
295 pylab.plot(t, v_hat, label='v_hat')
296 pylab.plot(t, w_hat, label='w_hat')
297 pylab.legend()
298
299 pylab.subplot(3, 1, 2)
300 pylab.plot(t, u, label='u')
301 pylab.plot(t, offset, label='voltage_offset')
302 pylab.legend()
303
304 pylab.subplot(3, 1, 3)
305 pylab.plot(t, a, label='a')
306 pylab.legend()
307
308 pylab.show()
Austin Schuh2e28d872022-02-19 18:25:57 -0800309
310def WriteCatapult(params, plant_files, controller_files, year_namespaces):
311 """Writes out the constants for a catapult to a file.
312
313 Args:
314 params: list of CatapultParams or CatapultParams, the
315 parameters defining the system.
316 plant_files: list of strings, the cc and h files for the plant.
317 controller_files: list of strings, the cc and h files for the integral
318 controller.
319 year_namespaces: list of strings, the namespace list to use.
320 """
321 # Write the generated constants out to a file.
322 catapults = []
323 integral_catapults = []
324
325 if type(params) is list:
326 name = params[0].name
327 for index, param in enumerate(params):
328 catapults.append(
329 Catapult(param, param.name + str(index)))
330 integral_catapults.append(
331 IntegralCatapult(param,
332 'Integral' + param.name + str(index)))
333 else:
334 name = params.name
335 catapults.append(Catapult(params, params.name))
336 integral_catapults.append(
337 IntegralCatapult(params, 'Integral' + params.name))
338
339 loop_writer = control_loop.ControlLoopWriter(
340 name, catapults, namespaces=year_namespaces)
341 loop_writer.AddConstant(
342 control_loop.Constant('kOutputRatio', '%f', catapults[0].G))
343 loop_writer.AddConstant(
344 control_loop.Constant('kFreeSpeed', '%f',
345 catapults[0].motor.free_speed))
346 loop_writer.Write(plant_files[0], plant_files[1])
347
348 integral_loop_writer = control_loop.ControlLoopWriter(
349 'Integral' + name, integral_catapults, namespaces=year_namespaces)
350 integral_loop_writer.Write(controller_files[0], controller_files[1])