blob: 8d375f8a7e2f953a978830c2c6c2ff38076cbb2f [file] [log] [blame]
Austin Schuh2e554032019-01-21 15:07:27 -08001#!/usr/bin/python
2
3from aos.util.trapezoid_profile import TrapezoidProfile
4from frc971.control_loops.python import control_loop
5from frc971.control_loops.python import controls
6import numpy
7from matplotlib import pylab
8import glog
9
10
11class AngularSystemParams(object):
12 def __init__(self,
13 name,
14 motor,
15 G,
16 J,
17 q_pos,
18 q_vel,
19 kalman_q_pos,
20 kalman_q_vel,
21 kalman_q_voltage,
22 kalman_r_position,
23 dt=0.005):
24 self.name = name
25 self.motor = motor
26 self.G = G
27 self.J = J
28 self.q_pos = q_pos
29 self.q_vel = q_vel
30 self.kalman_q_pos = kalman_q_pos
31 self.kalman_q_vel = kalman_q_vel
32 self.kalman_q_voltage = kalman_q_voltage
33 self.kalman_r_position = kalman_r_position
34 self.dt = dt
35
36
37class AngularSystem(control_loop.ControlLoop):
38 def __init__(self, params, name="AngularSystem"):
39 super(AngularSystem, self).__init__(name)
40 self.params = params
41
42 self.motor = params.motor
43
44 # Gear ratio
45 self.G = params.G
46
47 # Moment of inertia in kg m^2
48 self.J = params.J + self.motor.motor_inertia / (self.G ** 2.0)
49
50 # Control loop time step
51 self.dt = params.dt
52
53 # State is [position, velocity]
54 # Input is [Voltage]
55 C1 = self.motor.Kt / (self.G * self.G * self.motor.resistance *
56 self.J * self.motor.Kv)
57 C2 = self.motor.Kt / (self.G * self.J * self.motor.resistance)
58
59 self.A_continuous = numpy.matrix([[0, 1], [0, -C1]])
60
61 # Start with the unmodified input
62 self.B_continuous = numpy.matrix([[0], [C2]])
63 glog.debug(repr(self.A_continuous))
64 glog.debug(repr(self.B_continuous))
65
66 self.C = numpy.matrix([[1, 0]])
67 self.D = numpy.matrix([[0]])
68
69 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
70 self.B_continuous, self.dt)
71
72 controllability = controls.ctrb(self.A, self.B)
73 glog.debug('Controllability of %d',
74 numpy.linalg.matrix_rank(controllability))
75 glog.debug('J: %f', self.J)
76 glog.debug('Stall torque: %f', self.motor.stall_torque / self.G)
77 glog.debug('Stall acceleration: %f',
78 self.motor.stall_torque / self.G / self.J)
79
80 glog.debug('Free speed is %f',
81 -self.B_continuous[1, 0] / self.A_continuous[1, 1] * 12.0)
82
83 self.Q = numpy.matrix([[(1.0 / (self.params.q_pos**2.0)), 0.0],
84 [0.0, (1.0 / (self.params.q_vel**2.0))]])
85
86 self.R = numpy.matrix([[(1.0 / (12.0**2.0))]])
87 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
88
89 q_pos_ff = 0.005
90 q_vel_ff = 1.0
91 self.Qff = numpy.matrix([[(1.0 / (q_pos_ff**2.0)), 0.0],
92 [0.0, (1.0 / (q_vel_ff**2.0))]])
93
94 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
95
96 glog.debug('K %s', repr(self.K))
97 glog.debug('Poles are %s',
98 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
99
100 self.Q = numpy.matrix([[(self.params.kalman_q_pos**2.0), 0.0],
101 [0.0, (self.params.kalman_q_vel**2.0)]])
102
103 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
104
105 self.KalmanGain, self.Q_steady = controls.kalman(
106 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
107
108 glog.debug('Kal %s', repr(self.KalmanGain))
109
110 # The box formed by U_min and U_max must encompass all possible values,
111 # or else Austin's code gets angry.
112 self.U_max = numpy.matrix([[12.0]])
113 self.U_min = numpy.matrix([[-12.0]])
114
115 self.InitializeState()
116
117
118class IntegralAngularSystem(AngularSystem):
119 def __init__(self, params, name="IntegralAngularSystem"):
120 super(IntegralAngularSystem, self).__init__(params, name=name)
121
122 self.A_continuous_unaugmented = self.A_continuous
123 self.B_continuous_unaugmented = self.B_continuous
124
125 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
126 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
127 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
128
129 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
130 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
131
132 self.C_unaugmented = self.C
133 self.C = numpy.matrix(numpy.zeros((1, 3)))
134 self.C[0:1, 0:2] = self.C_unaugmented
135
136 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
137 self.B_continuous, self.dt)
138
139 self.Q = numpy.matrix(
140 [[(self.params.kalman_q_pos**2.0), 0.0, 0.0],
141 [0.0, (self.params.kalman_q_vel**2.0), 0.0],
142 [0.0, 0.0, (self.params.kalman_q_voltage**2.0)]])
143
144 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
145
146 self.KalmanGain, self.Q_steady = controls.kalman(
147 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
148
149 self.K_unaugmented = self.K
150 self.K = numpy.matrix(numpy.zeros((1, 3)))
151 self.K[0, 0:2] = self.K_unaugmented
152 self.K[0, 2] = 1
153
154 self.Kff = numpy.concatenate(
155 (self.Kff, numpy.matrix(numpy.zeros((1, 1)))), axis=1)
156
157 self.InitializeState()
158
159
160def RunTest(plant,
161 end_goal,
162 controller,
163 observer=None,
164 duration=1.0,
165 use_profile=True,
166 kick_time=0.5,
Lee Mracek28795ef2019-01-27 05:29:37 -0500167 kick_magnitude=0.0,
168 max_velocity=10.0,
169 max_acceleration=70.0):
Austin Schuh2e554032019-01-21 15:07:27 -0800170 """Runs the plant with an initial condition and goal.
171
172 Args:
173 plant: plant object to use.
174 end_goal: end_goal state.
175 controller: AngularSystem object to get K from, or None if we should
176 use plant.
177 observer: AngularSystem object to use for the observer, or None if we
178 should use the actual state.
179 duration: float, time in seconds to run the simulation for.
180 kick_time: float, time in seconds to kick the robot.
181 kick_magnitude: float, disturbance in volts to apply.
Lee Mracek28795ef2019-01-27 05:29:37 -0500182 max_velocity: float, The maximum velocity for the profile.
183 max_acceleration: float, The maximum acceleration for the profile.
Austin Schuh2e554032019-01-21 15:07:27 -0800184 """
185 t_plot = []
186 x_plot = []
187 v_plot = []
188 a_plot = []
189 x_goal_plot = []
190 v_goal_plot = []
191 x_hat_plot = []
192 u_plot = []
193 offset_plot = []
194
195 if controller is None:
196 controller = plant
197
198 vbat = 12.0
199
200 goal = numpy.concatenate(
201 (plant.X, numpy.matrix(numpy.zeros((1, 1)))), axis=0)
202
203 profile = TrapezoidProfile(plant.dt)
Lee Mracek28795ef2019-01-27 05:29:37 -0500204 profile.set_maximum_acceleration(max_acceleration)
205 profile.set_maximum_velocity(max_velocity)
Austin Schuh2e554032019-01-21 15:07:27 -0800206 profile.SetGoal(goal[0, 0])
207
208 U_last = numpy.matrix(numpy.zeros((1, 1)))
209 iterations = int(duration / plant.dt)
210 for i in xrange(iterations):
211 t = i * plant.dt
212 observer.Y = plant.Y
213 observer.CorrectObserver(U_last)
214
215 offset_plot.append(observer.X_hat[2, 0])
216 x_hat_plot.append(observer.X_hat[0, 0])
217
218 next_goal = numpy.concatenate(
219 (profile.Update(end_goal[0, 0], end_goal[1, 0]),
220 numpy.matrix(numpy.zeros((1, 1)))),
221 axis=0)
222
223 ff_U = controller.Kff * (next_goal - observer.A * goal)
224
225 if use_profile:
226 U_uncapped = controller.K * (goal - observer.X_hat) + ff_U
227 x_goal_plot.append(goal[0, 0])
228 v_goal_plot.append(goal[1, 0])
229 else:
230 U_uncapped = controller.K * (end_goal - observer.X_hat)
231 x_goal_plot.append(end_goal[0, 0])
232 v_goal_plot.append(end_goal[1, 0])
233
234 U = U_uncapped.copy()
235 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
236 x_plot.append(plant.X[0, 0])
237
238 if v_plot:
239 last_v = v_plot[-1]
240 else:
241 last_v = 0
242
243 v_plot.append(plant.X[1, 0])
244 a_plot.append((v_plot[-1] - last_v) / plant.dt)
245
246 u_offset = 0.0
247 if t >= kick_time:
248 u_offset = kick_magnitude
249 plant.Update(U + u_offset)
250
251 observer.PredictObserver(U)
252
253 t_plot.append(t)
254 u_plot.append(U[0, 0])
255
256 ff_U -= U_uncapped - U
257 goal = controller.A * goal + controller.B * ff_U
258
259 if U[0, 0] != U_uncapped[0, 0]:
260 profile.MoveCurrentState(
261 numpy.matrix([[goal[0, 0]], [goal[1, 0]]]))
262
263 glog.debug('Time: %f', t_plot[-1])
264 glog.debug('goal_error %s', repr(end_goal - goal))
265 glog.debug('error %s', repr(observer.X_hat - end_goal))
266
267 pylab.subplot(3, 1, 1)
268 pylab.plot(t_plot, x_plot, label='x')
269 pylab.plot(t_plot, x_hat_plot, label='x_hat')
270 pylab.plot(t_plot, x_goal_plot, label='x_goal')
271 pylab.legend()
272
273 pylab.subplot(3, 1, 2)
274 pylab.plot(t_plot, u_plot, label='u')
275 pylab.plot(t_plot, offset_plot, label='voltage_offset')
276 pylab.legend()
277
278 pylab.subplot(3, 1, 3)
279 pylab.plot(t_plot, a_plot, label='a')
280 pylab.legend()
281
282 pylab.show()
283
284
285def PlotStep(params, R):
286 """Plots a step move to the goal.
287
288 Args:
289 R: numpy.matrix(2, 1), the goal"""
290 plant = AngularSystem(params, params.name)
291 controller = IntegralAngularSystem(params, params.name)
292 observer = IntegralAngularSystem(params, params.name)
293
294 # Test moving the system.
295 initial_X = numpy.matrix([[0.0], [0.0]])
296 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
297 augmented_R[0:2, :] = R
298 RunTest(
299 plant,
300 end_goal=augmented_R,
301 controller=controller,
302 observer=observer,
303 duration=2.0,
304 use_profile=False,
305 kick_time=1.0,
306 kick_magnitude=0.0)
307
308
309def PlotKick(params, R):
310 """Plots a step motion with a kick at 1.0 seconds.
311
312 Args:
313 R: numpy.matrix(2, 1), the goal"""
314 plant = AngularSystem(params, params.name)
315 controller = IntegralAngularSystem(params, params.name)
316 observer = IntegralAngularSystem(params, params.name)
317
318 # Test moving the system.
319 initial_X = numpy.matrix([[0.0], [0.0]])
320 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
321 augmented_R[0:2, :] = R
322 RunTest(
323 plant,
324 end_goal=augmented_R,
325 controller=controller,
326 observer=observer,
327 duration=2.0,
328 use_profile=False,
329 kick_time=1.0,
330 kick_magnitude=2.0)
331
332
Lee Mracek28795ef2019-01-27 05:29:37 -0500333def PlotMotion(params, R, max_velocity=10.0, max_acceleration=70.0):
Austin Schuh2e554032019-01-21 15:07:27 -0800334 """Plots a trapezoidal motion.
335
336 Args:
337 R: numpy.matrix(2, 1), the goal,
Lee Mracek28795ef2019-01-27 05:29:37 -0500338 max_velocity: float, The max velocity of the profile.
339 max_acceleration: float, The max acceleration of the profile.
Austin Schuh2e554032019-01-21 15:07:27 -0800340 """
341 plant = AngularSystem(params, params.name)
342 controller = IntegralAngularSystem(params, params.name)
343 observer = IntegralAngularSystem(params, params.name)
344
345 # Test moving the system.
346 initial_X = numpy.matrix([[0.0], [0.0]])
347 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
348 augmented_R[0:2, :] = R
349 RunTest(
350 plant,
351 end_goal=augmented_R,
352 controller=controller,
353 observer=observer,
354 duration=2.0,
Lee Mracek28795ef2019-01-27 05:29:37 -0500355 use_profile=True,
356 max_velocity=max_velocity,
357 max_acceleration=max_acceleration)
Austin Schuh2e554032019-01-21 15:07:27 -0800358
359
360def WriteAngularSystem(params, plant_files, controller_files, year_namespaces):
361 """Writes out the constants for a angular system to a file.
362
363 Args:
364 params: AngularSystemParams, the parameters defining the system.
365 plant_files: list of strings, the cc and h files for the plant.
366 controller_files: list of strings, the cc and h files for the integral
367 controller.
368 year_namespaces: list of strings, the namespace list to use.
369 """
370 # Write the generated constants out to a file.
371 angular_system = AngularSystem(params, params.name)
372 loop_writer = control_loop.ControlLoopWriter(
373 angular_system.name, [angular_system], namespaces=year_namespaces)
374 loop_writer.Write(plant_files[0], plant_files[1])
375
376 integral_angular_system = IntegralAngularSystem(params,
377 'Integral' + params.name)
378 integral_loop_writer = control_loop.ControlLoopWriter(
379 integral_angular_system.name, [integral_angular_system],
380 namespaces=year_namespaces)
381 integral_loop_writer.Write(controller_files[0], controller_files[1])