blob: 042bd5c1b24328d42788423d07da3938ac62fc64 [file] [log] [blame]
Austin Schuhb5d302f2019-01-20 20:51:19 -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
Austin Schuhb5d302f2019-01-20 20:51:19 -08007from matplotlib import pylab
8import glog
9
10
11class LinearSystemParams(object):
Tyler Chatow6738c362019-02-16 14:12:30 -080012
Austin Schuhb5d302f2019-01-20 20:51:19 -080013 def __init__(self,
14 name,
15 motor,
16 G,
17 radius,
18 mass,
19 q_pos,
20 q_vel,
21 kalman_q_pos,
22 kalman_q_vel,
23 kalman_q_voltage,
24 kalman_r_position,
25 dt=0.005):
26 self.name = name
27 self.motor = motor
28 self.G = G
29 self.radius = radius
30 self.mass = mass
31 self.q_pos = q_pos
32 self.q_vel = q_vel
33 self.kalman_q_pos = kalman_q_pos
34 self.kalman_q_vel = kalman_q_vel
35 self.kalman_q_voltage = kalman_q_voltage
36 self.kalman_r_position = kalman_r_position
37 self.dt = dt
38
39
40class LinearSystem(control_loop.ControlLoop):
Tyler Chatow6738c362019-02-16 14:12:30 -080041
Austin Schuhb5d302f2019-01-20 20:51:19 -080042 def __init__(self, params, name='LinearSystem'):
43 super(LinearSystem, self).__init__(name)
44 self.params = params
45
46 self.motor = params.motor
47
48 # Gear ratio
49 self.G = params.G
50 self.radius = params.radius
51
Austin Schuh2e554032019-01-21 15:07:27 -080052 # Mass in kg
Austin Schuhb5d302f2019-01-20 20:51:19 -080053 self.mass = params.mass + self.motor.motor_inertia / (
54 (self.G * self.radius)**2.0)
55
56 # Control loop time step
57 self.dt = params.dt
58
59 # State is [position, velocity]
60 # Input is [Voltage]
61 C1 = self.motor.Kt / (self.G * self.G * self.radius * self.radius *
Tyler Chatow6738c362019-02-16 14:12:30 -080062 self.motor.resistance * self.mass * self.motor.Kv)
63 C2 = self.motor.Kt / (
64 self.G * self.radius * self.motor.resistance * self.mass)
Austin Schuhb5d302f2019-01-20 20:51:19 -080065
66 self.A_continuous = numpy.matrix([[0, 1], [0, -C1]])
67
68 # Start with the unmodified input
69 self.B_continuous = numpy.matrix([[0], [C2]])
70 glog.debug(repr(self.A_continuous))
71 glog.debug(repr(self.B_continuous))
72
73 self.C = numpy.matrix([[1, 0]])
74 self.D = numpy.matrix([[0]])
75
76 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
77 self.B_continuous, self.dt)
78
79 controllability = controls.ctrb(self.A, self.B)
80 glog.debug('Controllability of %d',
81 numpy.linalg.matrix_rank(controllability))
82 glog.debug('Mass: %f', self.mass)
Tyler Chatow6738c362019-02-16 14:12:30 -080083 glog.debug('Stall force: %f',
84 self.motor.stall_torque / self.G / self.radius)
85 glog.debug('Stall acceleration: %f',
86 self.motor.stall_torque / self.G / self.radius / self.mass)
Austin Schuhb5d302f2019-01-20 20:51:19 -080087
88 glog.debug('Free speed is %f',
89 -self.B_continuous[1, 0] / self.A_continuous[1, 1] * 12.0)
90
91 self.Q = numpy.matrix([[(1.0 / (self.params.q_pos**2.0)), 0.0],
92 [0.0, (1.0 / (self.params.q_vel**2.0))]])
93
94 self.R = numpy.matrix([[(1.0 / (12.0**2.0))]])
95 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
96
97 q_pos_ff = 0.005
98 q_vel_ff = 1.0
99 self.Qff = numpy.matrix([[(1.0 / (q_pos_ff**2.0)), 0.0],
100 [0.0, (1.0 / (q_vel_ff**2.0))]])
101
102 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
103
104 glog.debug('K %s', repr(self.K))
105 glog.debug('Poles are %s',
106 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
107
108 self.Q = numpy.matrix([[(self.params.kalman_q_pos**2.0), 0.0],
109 [0.0, (self.params.kalman_q_vel**2.0)]])
110
111 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
112
113 self.KalmanGain, self.Q_steady = controls.kalman(
114 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
115
116 glog.debug('Kal %s', repr(self.KalmanGain))
117
118 # The box formed by U_min and U_max must encompass all possible values,
119 # or else Austin's code gets angry.
120 self.U_max = numpy.matrix([[12.0]])
121 self.U_min = numpy.matrix([[-12.0]])
122
123 self.InitializeState()
124
125
126class IntegralLinearSystem(LinearSystem):
Tyler Chatow6738c362019-02-16 14:12:30 -0800127
Austin Schuhb5d302f2019-01-20 20:51:19 -0800128 def __init__(self, params, name='IntegralLinearSystem'):
129 super(IntegralLinearSystem, self).__init__(params, name=name)
130
Austin Schuhb5d302f2019-01-20 20:51:19 -0800131 self.A_continuous_unaugmented = self.A_continuous
132 self.B_continuous_unaugmented = self.B_continuous
133
134 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
135 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
136 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
137
138 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
139 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
140
141 self.C_unaugmented = self.C
142 self.C = numpy.matrix(numpy.zeros((1, 3)))
143 self.C[0:1, 0:2] = self.C_unaugmented
144
145 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
146 self.B_continuous, self.dt)
147
Tyler Chatow6738c362019-02-16 14:12:30 -0800148 self.Q = numpy.matrix([[(self.params.kalman_q_pos**2.0), 0.0, 0.0],
149 [0.0, (self.params.kalman_q_vel**2.0), 0.0],
150 [0.0, 0.0, (self.params.kalman_q_voltage**2.0)]])
Austin Schuhb5d302f2019-01-20 20:51:19 -0800151
152 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
153
154 self.KalmanGain, self.Q_steady = controls.kalman(
155 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
156
157 self.K_unaugmented = self.K
158 self.K = numpy.matrix(numpy.zeros((1, 3)))
159 self.K[0, 0:2] = self.K_unaugmented
160 self.K[0, 2] = 1
161
162 self.Kff = numpy.concatenate(
163 (self.Kff, numpy.matrix(numpy.zeros((1, 1)))), axis=1)
164
165 self.InitializeState()
166
167
168def RunTest(plant,
169 end_goal,
170 controller,
171 observer=None,
172 duration=1.0,
173 use_profile=True,
174 kick_time=0.5,
175 kick_magnitude=0.0,
Lee Mracek28795ef2019-01-27 05:29:37 -0500176 max_velocity=0.3,
177 max_acceleration=10.0):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800178 """Runs the plant with an initial condition and goal.
179
180 Args:
181 plant: plant object to use.
182 end_goal: end_goal state.
Austin Schuh2e554032019-01-21 15:07:27 -0800183 controller: LinearSystem object to get K from, or None if we should
Austin Schuhb5d302f2019-01-20 20:51:19 -0800184 use plant.
Austin Schuh2e554032019-01-21 15:07:27 -0800185 observer: LinearSystem object to use for the observer, or None if we
186 should use the actual state.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800187 duration: float, time in seconds to run the simulation for.
188 kick_time: float, time in seconds to kick the robot.
189 kick_magnitude: float, disturbance in volts to apply.
190 max_velocity: float, the max speed in m/s to profile.
Lee Mracek28795ef2019-01-27 05:29:37 -0500191 max_acceleration: float, the max acceleration in m/s/s to profile.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800192 """
193 t_plot = []
194 x_plot = []
195 v_plot = []
196 a_plot = []
197 x_goal_plot = []
198 v_goal_plot = []
199 x_hat_plot = []
200 u_plot = []
201 offset_plot = []
202
203 if controller is None:
204 controller = plant
205
206 vbat = 12.0
207
Tyler Chatow6738c362019-02-16 14:12:30 -0800208 goal = numpy.concatenate((plant.X, numpy.matrix(numpy.zeros((1, 1)))),
209 axis=0)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800210
211 profile = TrapezoidProfile(plant.dt)
Lee Mracek28795ef2019-01-27 05:29:37 -0500212 profile.set_maximum_acceleration(max_acceleration)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800213 profile.set_maximum_velocity(max_velocity)
214 profile.SetGoal(goal[0, 0])
215
216 U_last = numpy.matrix(numpy.zeros((1, 1)))
217 iterations = int(duration / plant.dt)
218 for i in xrange(iterations):
219 t = i * plant.dt
220 observer.Y = plant.Y
221 observer.CorrectObserver(U_last)
222
223 offset_plot.append(observer.X_hat[2, 0])
224 x_hat_plot.append(observer.X_hat[0, 0])
225
226 next_goal = numpy.concatenate(
227 (profile.Update(end_goal[0, 0], end_goal[1, 0]),
228 numpy.matrix(numpy.zeros((1, 1)))),
229 axis=0)
230
231 ff_U = controller.Kff * (next_goal - observer.A * goal)
232
233 if use_profile:
234 U_uncapped = controller.K * (goal - observer.X_hat) + ff_U
235 x_goal_plot.append(goal[0, 0])
236 v_goal_plot.append(goal[1, 0])
237 else:
238 U_uncapped = controller.K * (end_goal - observer.X_hat)
239 x_goal_plot.append(end_goal[0, 0])
240 v_goal_plot.append(end_goal[1, 0])
241
242 U = U_uncapped.copy()
243 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
244 x_plot.append(plant.X[0, 0])
245
246 if v_plot:
247 last_v = v_plot[-1]
248 else:
249 last_v = 0
250
251 v_plot.append(plant.X[1, 0])
252 a_plot.append((v_plot[-1] - last_v) / plant.dt)
253
254 u_offset = 0.0
255 if t >= kick_time:
256 u_offset = kick_magnitude
257 plant.Update(U + u_offset)
258
259 observer.PredictObserver(U)
260
261 t_plot.append(t)
262 u_plot.append(U[0, 0])
263
264 ff_U -= U_uncapped - U
265 goal = controller.A * goal + controller.B * ff_U
266
267 if U[0, 0] != U_uncapped[0, 0]:
Tyler Chatow6738c362019-02-16 14:12:30 -0800268 profile.MoveCurrentState(numpy.matrix([[goal[0, 0]], [goal[1, 0]]]))
Austin Schuhb5d302f2019-01-20 20:51:19 -0800269
270 glog.debug('Time: %f', t_plot[-1])
271 glog.debug('goal_error %s', repr(end_goal - goal))
272 glog.debug('error %s', repr(observer.X_hat - end_goal))
273
274 pylab.subplot(3, 1, 1)
275 pylab.plot(t_plot, x_plot, label='x')
276 pylab.plot(t_plot, x_hat_plot, label='x_hat')
277 pylab.plot(t_plot, x_goal_plot, label='x_goal')
278 pylab.legend()
279
280 pylab.subplot(3, 1, 2)
281 pylab.plot(t_plot, u_plot, label='u')
282 pylab.plot(t_plot, offset_plot, label='voltage_offset')
283 pylab.legend()
284
285 pylab.subplot(3, 1, 3)
286 pylab.plot(t_plot, a_plot, label='a')
287 pylab.legend()
288
289 pylab.show()
290
291
Austin Schuh9d9d3742019-02-15 23:00:13 -0800292def PlotStep(params, R, plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800293 """Plots a step move to the goal.
294
295 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800296 params: LinearSystemParams for the controller and observer
297 plant_params: LinearSystemParams for the plant. Defaults to params if
298 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800299 R: numpy.matrix(2, 1), the goal"""
Austin Schuh9d9d3742019-02-15 23:00:13 -0800300 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800301 controller = IntegralLinearSystem(params, params.name)
302 observer = IntegralLinearSystem(params, params.name)
303
304 # Test moving the system.
305 initial_X = numpy.matrix([[0.0], [0.0]])
306 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
307 augmented_R[0:2, :] = R
308 RunTest(
309 plant,
310 end_goal=augmented_R,
311 controller=controller,
312 observer=observer,
313 duration=2.0,
314 use_profile=False,
315 kick_time=1.0,
316 kick_magnitude=0.0)
317
318
Austin Schuh9d9d3742019-02-15 23:00:13 -0800319def PlotKick(params, R, plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800320 """Plots a step motion with a kick at 1.0 seconds.
321
322 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800323 params: LinearSystemParams for the controller and observer
324 plant_params: LinearSystemParams for the plant. Defaults to params if
325 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800326 R: numpy.matrix(2, 1), the goal"""
Austin Schuh9d9d3742019-02-15 23:00:13 -0800327 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800328 controller = IntegralLinearSystem(params, params.name)
329 observer = IntegralLinearSystem(params, params.name)
330
331 # Test moving the system.
332 initial_X = numpy.matrix([[0.0], [0.0]])
333 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
334 augmented_R[0:2, :] = R
335 RunTest(
336 plant,
337 end_goal=augmented_R,
338 controller=controller,
339 observer=observer,
340 duration=2.0,
341 use_profile=False,
342 kick_time=1.0,
343 kick_magnitude=2.0)
344
345
Austin Schuh9d9d3742019-02-15 23:00:13 -0800346def PlotMotion(params,
347 R,
348 max_velocity=0.3,
349 max_acceleration=10.0,
350 plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800351 """Plots a trapezoidal motion.
352
353 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800354 params: LinearSystemParams for the controller and observer
355 plant_params: LinearSystemParams for the plant. Defaults to params if
356 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800357 R: numpy.matrix(2, 1), the goal,
358 max_velocity: float, The max velocity of the profile.
Lee Mracek28795ef2019-01-27 05:29:37 -0500359 max_acceleration: float, The max acceleration of the profile.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800360 """
Austin Schuh9d9d3742019-02-15 23:00:13 -0800361 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800362 controller = IntegralLinearSystem(params, params.name)
363 observer = IntegralLinearSystem(params, params.name)
364
365 # Test moving the system.
366 initial_X = numpy.matrix([[0.0], [0.0]])
367 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
368 augmented_R[0:2, :] = R
369 RunTest(
370 plant,
371 end_goal=augmented_R,
372 controller=controller,
373 observer=observer,
374 duration=2.0,
375 use_profile=True,
Lee Mracek28795ef2019-01-27 05:29:37 -0500376 max_velocity=max_velocity,
377 max_acceleration=max_acceleration)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800378
379
380def WriteLinearSystem(params, plant_files, controller_files, year_namespaces):
381 """Writes out the constants for a linear system to a file.
382
383 Args:
384 params: LinearSystemParams, the parameters defining the system.
385 plant_files: list of strings, the cc and h files for the plant.
386 controller_files: list of strings, the cc and h files for the integral
387 controller.
388 year_namespaces: list of strings, the namespace list to use.
389 """
390 # Write the generated constants out to a file.
391 linear_system = LinearSystem(params, params.name)
392 loop_writer = control_loop.ControlLoopWriter(
393 linear_system.name, [linear_system], namespaces=year_namespaces)
394 loop_writer.AddConstant(
Tyler Chatow6738c362019-02-16 14:12:30 -0800395 control_loop.Constant('kFreeSpeed', '%f',
396 linear_system.motor.free_speed))
Austin Schuhb5d302f2019-01-20 20:51:19 -0800397 loop_writer.AddConstant(
Tyler Chatow6738c362019-02-16 14:12:30 -0800398 control_loop.Constant('kOutputRatio', '%f',
399 linear_system.G * linear_system.radius))
Alex Perry5fb5ff22019-02-09 21:53:17 -0800400 loop_writer.AddConstant(
401 control_loop.Constant('kRadius', '%f', linear_system.radius))
Austin Schuhb5d302f2019-01-20 20:51:19 -0800402 loop_writer.Write(plant_files[0], plant_files[1])
403
404 integral_linear_system = IntegralLinearSystem(params,
405 'Integral' + params.name)
406 integral_loop_writer = control_loop.ControlLoopWriter(
407 integral_linear_system.name, [integral_linear_system],
408 namespaces=year_namespaces)
409 integral_loop_writer.Write(controller_files[0], controller_files[1])