blob: 90faf9f2517b8a94fb85a6b2c9a241fcdccf4cfc [file] [log] [blame]
Austin Schuh3c542312013-02-24 01:53:50 -08001import controls
2import numpy
3
Austin Schuhe3490622013-03-13 01:24:30 -07004class ControlLoopWriter(object):
5 def __init__(self, gain_schedule_name, loops, namespaces=None):
6 """Constructs a control loop writer.
7
8 Args:
9 gain_schedule_name: string, Name of the overall controller.
10 loops: array[ControlLoop], a list of control loops to gain schedule
11 in order.
12 namespaces: array[string], a list of names of namespaces to nest in
13 order. If None, the default will be used.
14 """
15 self._gain_schedule_name = gain_schedule_name
16 self._loops = loops
17 if namespaces:
18 self._namespaces = namespaces
19 else:
20 self._namespaces = ['frc971', 'control_loops']
21
22 self._namespace_start = '\n'.join(
23 ['namespace %s {' % name for name in self._namespaces])
24
25 self._namespace_end = '\n'.join(
26 ['} // namespace %s' % name for name in reversed(self._namespaces)])
27
Brian Silvermane51ad632014-01-08 15:12:29 -080028 def _TopDirectory(self):
29 return self._namespaces[0]
30
Austin Schuhe3490622013-03-13 01:24:30 -070031 def _HeaderGuard(self, header_file):
Brian Silvermane51ad632014-01-08 15:12:29 -080032 return (self._TopDirectory().upper() + '_CONTROL_LOOPS_' +
Austin Schuhe3490622013-03-13 01:24:30 -070033 header_file.upper().replace('.', '_').replace('/', '_') +
34 '_')
35
36 def Write(self, header_file, cc_file):
37 """Writes the loops to the specified files."""
38 self.WriteHeader(header_file)
39 self.WriteCC(header_file, cc_file)
40
41 def _GenericType(self, typename):
42 """Returns a loop template using typename for the type."""
43 num_states = self._loops[0].A.shape[0]
44 num_inputs = self._loops[0].B.shape[1]
45 num_outputs = self._loops[0].C.shape[0]
46 return '%s<%d, %d, %d>' % (
47 typename, num_states, num_inputs, num_outputs)
48
49 def _ControllerType(self):
50 """Returns a template name for StateFeedbackController."""
51 return self._GenericType('StateFeedbackController')
52
53 def _LoopType(self):
54 """Returns a template name for StateFeedbackLoop."""
55 return self._GenericType('StateFeedbackLoop')
56
57 def _PlantType(self):
58 """Returns a template name for StateFeedbackPlant."""
59 return self._GenericType('StateFeedbackPlant')
60
61 def _CoeffType(self):
62 """Returns a template name for StateFeedbackPlantCoefficients."""
63 return self._GenericType('StateFeedbackPlantCoefficients')
64
James Kuszmaul0e866512014-02-21 13:12:52 -080065 def WriteHeader(self, header_file, double_appendage=False, MoI_ratio=0.0):
66 """Writes the header file to the file named header_file.
67 Set double_appendage to true in order to include a ratio of
68 moments of inertia constant. Currently, only used for 2014 claw."""
Austin Schuhe3490622013-03-13 01:24:30 -070069 with open(header_file, 'w') as fd:
70 header_guard = self._HeaderGuard(header_file)
71 fd.write('#ifndef %s\n'
72 '#define %s\n\n' % (header_guard, header_guard))
73 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
74 fd.write('\n')
75
76 fd.write(self._namespace_start)
77 fd.write('\n\n')
78 for loop in self._loops:
79 fd.write(loop.DumpPlantHeader())
80 fd.write('\n')
81 fd.write(loop.DumpControllerHeader())
82 fd.write('\n')
83
84 fd.write('%s Make%sPlant();\n\n' %
85 (self._PlantType(), self._gain_schedule_name))
86
87 fd.write('%s Make%sLoop();\n\n' %
88 (self._LoopType(), self._gain_schedule_name))
89
James Kuszmaul0e866512014-02-21 13:12:52 -080090 fd.write('const double k%sMomentOfInertiaRatio = %f;\n\n' %
91 (self._gain_schedule_name,
92 self._loops[0].J_top / self._loops[0].J_bottom))
93
Austin Schuhe3490622013-03-13 01:24:30 -070094 fd.write(self._namespace_end)
95 fd.write('\n\n')
96 fd.write("#endif // %s\n" % header_guard)
97
98 def WriteCC(self, header_file_name, cc_file):
99 """Writes the cc file to the file named cc_file."""
100 with open(cc_file, 'w') as fd:
Brian Silvermandf3e7b22013-11-08 19:43:27 -0800101 fd.write('#include \"%s/control_loops/%s\"\n' %
Brian Silvermane51ad632014-01-08 15:12:29 -0800102 (self._TopDirectory(), header_file_name))
Austin Schuhe3490622013-03-13 01:24:30 -0700103 fd.write('\n')
104 fd.write('#include <vector>\n')
105 fd.write('\n')
106 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
107 fd.write('\n')
108 fd.write(self._namespace_start)
109 fd.write('\n\n')
110 for loop in self._loops:
111 fd.write(loop.DumpPlant())
112 fd.write('\n')
113
114 for loop in self._loops:
115 fd.write(loop.DumpController())
116 fd.write('\n')
117
118 fd.write('%s Make%sPlant() {\n' %
119 (self._PlantType(), self._gain_schedule_name))
120 fd.write(' ::std::vector<%s *> plants(%d);\n' % (
121 self._CoeffType(), len(self._loops)))
122 for index, loop in enumerate(self._loops):
123 fd.write(' plants[%d] = new %s(%s);\n' %
124 (index, self._CoeffType(),
125 loop.PlantFunction()))
126 fd.write(' return %s(plants);\n' % self._PlantType())
127 fd.write('}\n\n')
128
129 fd.write('%s Make%sLoop() {\n' %
130 (self._LoopType(), self._gain_schedule_name))
131 fd.write(' ::std::vector<%s *> controllers(%d);\n' % (
132 self._ControllerType(), len(self._loops)))
133 for index, loop in enumerate(self._loops):
134 fd.write(' controllers[%d] = new %s(%s);\n' %
135 (index, self._ControllerType(),
136 loop.ControllerFunction()))
137 fd.write(' return %s(controllers);\n' % self._LoopType())
138 fd.write('}\n\n')
139
140 fd.write(self._namespace_end)
141 fd.write('\n')
142
143
Austin Schuh3c542312013-02-24 01:53:50 -0800144class ControlLoop(object):
145 def __init__(self, name):
146 """Constructs a control loop object.
147
148 Args:
149 name: string, The name of the loop to use when writing the C++ files.
150 """
151 self._name = name
152
Austin Schuhc1f68892013-03-16 17:06:27 -0700153 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt):
154 """Calculates the discrete time values for A and B.
Austin Schuh3c542312013-02-24 01:53:50 -0800155
156 Args:
157 A_continuous: numpy.matrix, The continuous time A matrix
158 B_continuous: numpy.matrix, The continuous time B matrix
159 dt: float, The time step of the control loop
Austin Schuhc1f68892013-03-16 17:06:27 -0700160
161 Returns:
162 (A, B), numpy.matrix, the control matricies.
Austin Schuh3c542312013-02-24 01:53:50 -0800163 """
Austin Schuhc1f68892013-03-16 17:06:27 -0700164 return controls.c2d(A_continuous, B_continuous, dt)
165
166 def InitializeState(self):
167 """Sets X, Y, and X_hat to zero defaults."""
Austin Schuh3c542312013-02-24 01:53:50 -0800168 self.X = numpy.zeros((self.A.shape[0], 1))
Austin Schuhc1f68892013-03-16 17:06:27 -0700169 self.Y = self.C * self.X
Austin Schuh3c542312013-02-24 01:53:50 -0800170 self.X_hat = numpy.zeros((self.A.shape[0], 1))
171
172 def PlaceControllerPoles(self, poles):
173 """Places the controller poles.
174
175 Args:
176 poles: array, An array of poles. Must be complex conjegates if they have
177 any imaginary portions.
178 """
179 self.K = controls.dplace(self.A, self.B, poles)
180
181 def PlaceObserverPoles(self, poles):
182 """Places the observer poles.
183
184 Args:
185 poles: array, An array of poles. Must be complex conjegates if they have
186 any imaginary portions.
187 """
188 self.L = controls.dplace(self.A.T, self.C.T, poles).T
189
190 def Update(self, U):
191 """Simulates one time step with the provided U."""
James Kuszmaulc02a39a2014-02-18 15:45:16 -0800192 #U = numpy.clip(U, self.U_min, self.U_max)
Austin Schuh3c542312013-02-24 01:53:50 -0800193 self.X = self.A * self.X + self.B * U
194 self.Y = self.C * self.X + self.D * U
195
196 def UpdateObserver(self, U):
197 """Updates the observer given the provided U."""
198 self.X_hat = (self.A * self.X_hat + self.B * U +
199 self.L * (self.Y - self.C * self.X_hat - self.D * U))
200
201 def _DumpMatrix(self, matrix_name, matrix):
202 """Dumps the provided matrix into a variable called matrix_name.
203
204 Args:
205 matrix_name: string, The variable name to save the matrix to.
206 matrix: The matrix to dump.
207
208 Returns:
209 string, The C++ commands required to populate a variable named matrix_name
210 with the contents of matrix.
211 """
Austin Schuhe3490622013-03-13 01:24:30 -0700212 ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
Austin Schuh3c542312013-02-24 01:53:50 -0800213 matrix.shape[0], matrix.shape[1], matrix_name)]
214 first = True
Brian Silverman0f637382013-03-03 17:44:46 -0800215 for x in xrange(matrix.shape[0]):
216 for y in xrange(matrix.shape[1]):
Austin Schuh7ec34fd2014-02-15 22:27:46 -0800217 element = matrix[x, y]
Brian Silverman0f637382013-03-03 17:44:46 -0800218 if first:
Austin Schuhe3490622013-03-13 01:24:30 -0700219 ans.append(' %s << ' % matrix_name)
Brian Silverman0f637382013-03-03 17:44:46 -0800220 first = False
221 else:
Austin Schuhe3490622013-03-13 01:24:30 -0700222 ans.append(', ')
Brian Silverman0f637382013-03-03 17:44:46 -0800223 ans.append(str(element))
Austin Schuh3c542312013-02-24 01:53:50 -0800224
Austin Schuhe3490622013-03-13 01:24:30 -0700225 ans.append(';\n')
226 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800227
Austin Schuhe3490622013-03-13 01:24:30 -0700228 def DumpPlantHeader(self):
Austin Schuh3c542312013-02-24 01:53:50 -0800229 """Writes out a c++ header declaration which will create a Plant object.
230
Austin Schuh3c542312013-02-24 01:53:50 -0800231 Returns:
232 string, The header declaration for the function.
233 """
234 num_states = self.A.shape[0]
235 num_inputs = self.B.shape[1]
236 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700237 return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
238 num_states, num_inputs, num_outputs, self._name)
Austin Schuh3c542312013-02-24 01:53:50 -0800239
Austin Schuhe3490622013-03-13 01:24:30 -0700240 def DumpPlant(self):
241 """Writes out a c++ function which will create a PlantCoefficients object.
Austin Schuh3c542312013-02-24 01:53:50 -0800242
243 Returns:
244 string, The function which will create the object.
245 """
246 num_states = self.A.shape[0]
247 num_inputs = self.B.shape[1]
248 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700249 ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
250 ' Make%sPlantCoefficients() {\n' % (
251 num_states, num_inputs, num_outputs, self._name)]
Austin Schuh3c542312013-02-24 01:53:50 -0800252
Austin Schuhe3490622013-03-13 01:24:30 -0700253 ans.append(self._DumpMatrix('A', self.A))
254 ans.append(self._DumpMatrix('B', self.B))
255 ans.append(self._DumpMatrix('C', self.C))
256 ans.append(self._DumpMatrix('D', self.D))
257 ans.append(self._DumpMatrix('U_max', self.U_max))
258 ans.append(self._DumpMatrix('U_min', self.U_min))
Austin Schuh3c542312013-02-24 01:53:50 -0800259
Austin Schuhe3490622013-03-13 01:24:30 -0700260 ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
261 '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
Austin Schuh3c542312013-02-24 01:53:50 -0800262 num_outputs))
Austin Schuhe3490622013-03-13 01:24:30 -0700263 ans.append('}\n')
264 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800265
Austin Schuhe3490622013-03-13 01:24:30 -0700266 def PlantFunction(self):
267 """Returns the name of the plant coefficient function."""
268 return 'Make%sPlantCoefficients()' % self._name
Austin Schuh3c542312013-02-24 01:53:50 -0800269
Austin Schuhe3490622013-03-13 01:24:30 -0700270 def ControllerFunction(self):
271 """Returns the name of the controller function."""
272 return 'Make%sController()' % self._name
273
274 def DumpControllerHeader(self):
275 """Writes out a c++ header declaration which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800276
277 Returns:
278 string, The header declaration for the function.
279 """
280 num_states = self.A.shape[0]
281 num_inputs = self.B.shape[1]
282 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700283 return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
284 num_states, num_inputs, num_outputs, self.ControllerFunction())
Austin Schuh3c542312013-02-24 01:53:50 -0800285
Austin Schuhe3490622013-03-13 01:24:30 -0700286 def DumpController(self):
287 """Returns a c++ function which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800288
289 Returns:
290 string, The function which will create the object.
291 """
292 num_states = self.A.shape[0]
293 num_inputs = self.B.shape[1]
294 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700295 ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
296 num_states, num_inputs, num_outputs, self.ControllerFunction())]
Austin Schuh3c542312013-02-24 01:53:50 -0800297
Austin Schuhe3490622013-03-13 01:24:30 -0700298 ans.append(self._DumpMatrix('L', self.L))
299 ans.append(self._DumpMatrix('K', self.K))
Austin Schuh3c542312013-02-24 01:53:50 -0800300
Austin Schuhe3490622013-03-13 01:24:30 -0700301 ans.append(' return StateFeedbackController<%d, %d, %d>'
302 '(L, K, Make%sPlantCoefficients());\n' % (num_states, num_inputs,
303 num_outputs, self._name))
304 ans.append('}\n')
305 return ''.join(ans)