blob: 754ba62298a3f6b1b6736545ae8c4e1e6296a5e9 [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
28 def _HeaderGuard(self, header_file):
29 return ('FRC971_CONTROL_LOOPS_' +
30 header_file.upper().replace('.', '_').replace('/', '_') +
31 '_')
32
33 def Write(self, header_file, cc_file):
34 """Writes the loops to the specified files."""
35 self.WriteHeader(header_file)
36 self.WriteCC(header_file, cc_file)
37
38 def _GenericType(self, typename):
39 """Returns a loop template using typename for the type."""
40 num_states = self._loops[0].A.shape[0]
41 num_inputs = self._loops[0].B.shape[1]
42 num_outputs = self._loops[0].C.shape[0]
43 return '%s<%d, %d, %d>' % (
44 typename, num_states, num_inputs, num_outputs)
45
46 def _ControllerType(self):
47 """Returns a template name for StateFeedbackController."""
48 return self._GenericType('StateFeedbackController')
49
50 def _LoopType(self):
51 """Returns a template name for StateFeedbackLoop."""
52 return self._GenericType('StateFeedbackLoop')
53
54 def _PlantType(self):
55 """Returns a template name for StateFeedbackPlant."""
56 return self._GenericType('StateFeedbackPlant')
57
58 def _CoeffType(self):
59 """Returns a template name for StateFeedbackPlantCoefficients."""
60 return self._GenericType('StateFeedbackPlantCoefficients')
61
62 def WriteHeader(self, header_file):
63 """Writes the header file to the file named header_file."""
64 with open(header_file, 'w') as fd:
65 header_guard = self._HeaderGuard(header_file)
66 fd.write('#ifndef %s\n'
67 '#define %s\n\n' % (header_guard, header_guard))
68 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
69 fd.write('\n')
70
71 fd.write(self._namespace_start)
72 fd.write('\n\n')
73 for loop in self._loops:
74 fd.write(loop.DumpPlantHeader())
75 fd.write('\n')
76 fd.write(loop.DumpControllerHeader())
77 fd.write('\n')
78
79 fd.write('%s Make%sPlant();\n\n' %
80 (self._PlantType(), self._gain_schedule_name))
81
82 fd.write('%s Make%sLoop();\n\n' %
83 (self._LoopType(), self._gain_schedule_name))
84
85 fd.write(self._namespace_end)
86 fd.write('\n\n')
87 fd.write("#endif // %s\n" % header_guard)
88
89 def WriteCC(self, header_file_name, cc_file):
90 """Writes the cc file to the file named cc_file."""
91 with open(cc_file, 'w') as fd:
92 fd.write('#include \"frc971/control_loops/%s\"\n' % header_file_name)
93 fd.write('\n')
94 fd.write('#include <vector>\n')
95 fd.write('\n')
96 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
97 fd.write('\n')
98 fd.write(self._namespace_start)
99 fd.write('\n\n')
100 for loop in self._loops:
101 fd.write(loop.DumpPlant())
102 fd.write('\n')
103
104 for loop in self._loops:
105 fd.write(loop.DumpController())
106 fd.write('\n')
107
108 fd.write('%s Make%sPlant() {\n' %
109 (self._PlantType(), self._gain_schedule_name))
110 fd.write(' ::std::vector<%s *> plants(%d);\n' % (
111 self._CoeffType(), len(self._loops)))
112 for index, loop in enumerate(self._loops):
113 fd.write(' plants[%d] = new %s(%s);\n' %
114 (index, self._CoeffType(),
115 loop.PlantFunction()))
116 fd.write(' return %s(plants);\n' % self._PlantType())
117 fd.write('}\n\n')
118
119 fd.write('%s Make%sLoop() {\n' %
120 (self._LoopType(), self._gain_schedule_name))
121 fd.write(' ::std::vector<%s *> controllers(%d);\n' % (
122 self._ControllerType(), len(self._loops)))
123 for index, loop in enumerate(self._loops):
124 fd.write(' controllers[%d] = new %s(%s);\n' %
125 (index, self._ControllerType(),
126 loop.ControllerFunction()))
127 fd.write(' return %s(controllers);\n' % self._LoopType())
128 fd.write('}\n\n')
129
130 fd.write(self._namespace_end)
131 fd.write('\n')
132
133
Austin Schuh3c542312013-02-24 01:53:50 -0800134class ControlLoop(object):
135 def __init__(self, name):
136 """Constructs a control loop object.
137
138 Args:
139 name: string, The name of the loop to use when writing the C++ files.
140 """
141 self._name = name
142
Austin Schuhc1f68892013-03-16 17:06:27 -0700143 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt):
144 """Calculates the discrete time values for A and B.
Austin Schuh3c542312013-02-24 01:53:50 -0800145
146 Args:
147 A_continuous: numpy.matrix, The continuous time A matrix
148 B_continuous: numpy.matrix, The continuous time B matrix
149 dt: float, The time step of the control loop
Austin Schuhc1f68892013-03-16 17:06:27 -0700150
151 Returns:
152 (A, B), numpy.matrix, the control matricies.
Austin Schuh3c542312013-02-24 01:53:50 -0800153 """
Austin Schuhc1f68892013-03-16 17:06:27 -0700154 return controls.c2d(A_continuous, B_continuous, dt)
155
156 def InitializeState(self):
157 """Sets X, Y, and X_hat to zero defaults."""
Austin Schuh3c542312013-02-24 01:53:50 -0800158 self.X = numpy.zeros((self.A.shape[0], 1))
Austin Schuhc1f68892013-03-16 17:06:27 -0700159 self.Y = self.C * self.X
Austin Schuh3c542312013-02-24 01:53:50 -0800160 self.X_hat = numpy.zeros((self.A.shape[0], 1))
161
162 def PlaceControllerPoles(self, poles):
163 """Places the controller poles.
164
165 Args:
166 poles: array, An array of poles. Must be complex conjegates if they have
167 any imaginary portions.
168 """
169 self.K = controls.dplace(self.A, self.B, poles)
170
171 def PlaceObserverPoles(self, poles):
172 """Places the observer poles.
173
174 Args:
175 poles: array, An array of poles. Must be complex conjegates if they have
176 any imaginary portions.
177 """
178 self.L = controls.dplace(self.A.T, self.C.T, poles).T
179
180 def Update(self, U):
181 """Simulates one time step with the provided U."""
182 U = numpy.clip(U, self.U_min, self.U_max)
183 self.X = self.A * self.X + self.B * U
184 self.Y = self.C * self.X + self.D * U
185
186 def UpdateObserver(self, U):
187 """Updates the observer given the provided U."""
188 self.X_hat = (self.A * self.X_hat + self.B * U +
189 self.L * (self.Y - self.C * self.X_hat - self.D * U))
190
191 def _DumpMatrix(self, matrix_name, matrix):
192 """Dumps the provided matrix into a variable called matrix_name.
193
194 Args:
195 matrix_name: string, The variable name to save the matrix to.
196 matrix: The matrix to dump.
197
198 Returns:
199 string, The C++ commands required to populate a variable named matrix_name
200 with the contents of matrix.
201 """
Austin Schuhe3490622013-03-13 01:24:30 -0700202 ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
Austin Schuh3c542312013-02-24 01:53:50 -0800203 matrix.shape[0], matrix.shape[1], matrix_name)]
204 first = True
Brian Silverman0f637382013-03-03 17:44:46 -0800205 for x in xrange(matrix.shape[0]):
206 for y in xrange(matrix.shape[1]):
207 element = matrix[x, y]
208 if first:
Austin Schuhe3490622013-03-13 01:24:30 -0700209 ans.append(' %s << ' % matrix_name)
Brian Silverman0f637382013-03-03 17:44:46 -0800210 first = False
211 else:
Austin Schuhe3490622013-03-13 01:24:30 -0700212 ans.append(', ')
Brian Silverman0f637382013-03-03 17:44:46 -0800213 ans.append(str(element))
Austin Schuh3c542312013-02-24 01:53:50 -0800214
Austin Schuhe3490622013-03-13 01:24:30 -0700215 ans.append(';\n')
216 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800217
Austin Schuhe3490622013-03-13 01:24:30 -0700218 def DumpPlantHeader(self):
Austin Schuh3c542312013-02-24 01:53:50 -0800219 """Writes out a c++ header declaration which will create a Plant object.
220
Austin Schuh3c542312013-02-24 01:53:50 -0800221 Returns:
222 string, The header declaration for the function.
223 """
224 num_states = self.A.shape[0]
225 num_inputs = self.B.shape[1]
226 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700227 return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
228 num_states, num_inputs, num_outputs, self._name)
Austin Schuh3c542312013-02-24 01:53:50 -0800229
Austin Schuhe3490622013-03-13 01:24:30 -0700230 def DumpPlant(self):
231 """Writes out a c++ function which will create a PlantCoefficients object.
Austin Schuh3c542312013-02-24 01:53:50 -0800232
233 Returns:
234 string, The function which will create the object.
235 """
236 num_states = self.A.shape[0]
237 num_inputs = self.B.shape[1]
238 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700239 ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
240 ' Make%sPlantCoefficients() {\n' % (
241 num_states, num_inputs, num_outputs, self._name)]
Austin Schuh3c542312013-02-24 01:53:50 -0800242
Austin Schuhe3490622013-03-13 01:24:30 -0700243 ans.append(self._DumpMatrix('A', self.A))
244 ans.append(self._DumpMatrix('B', self.B))
245 ans.append(self._DumpMatrix('C', self.C))
246 ans.append(self._DumpMatrix('D', self.D))
247 ans.append(self._DumpMatrix('U_max', self.U_max))
248 ans.append(self._DumpMatrix('U_min', self.U_min))
Austin Schuh3c542312013-02-24 01:53:50 -0800249
Austin Schuhe3490622013-03-13 01:24:30 -0700250 ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
251 '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
Austin Schuh3c542312013-02-24 01:53:50 -0800252 num_outputs))
Austin Schuhe3490622013-03-13 01:24:30 -0700253 ans.append('}\n')
254 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800255
Austin Schuhe3490622013-03-13 01:24:30 -0700256 def PlantFunction(self):
257 """Returns the name of the plant coefficient function."""
258 return 'Make%sPlantCoefficients()' % self._name
Austin Schuh3c542312013-02-24 01:53:50 -0800259
Austin Schuhe3490622013-03-13 01:24:30 -0700260 def ControllerFunction(self):
261 """Returns the name of the controller function."""
262 return 'Make%sController()' % self._name
263
264 def DumpControllerHeader(self):
265 """Writes out a c++ header declaration which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800266
267 Returns:
268 string, The header declaration for the function.
269 """
270 num_states = self.A.shape[0]
271 num_inputs = self.B.shape[1]
272 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700273 return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
274 num_states, num_inputs, num_outputs, self.ControllerFunction())
Austin Schuh3c542312013-02-24 01:53:50 -0800275
Austin Schuhe3490622013-03-13 01:24:30 -0700276 def DumpController(self):
277 """Returns a c++ function which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800278
279 Returns:
280 string, The function which will create the object.
281 """
282 num_states = self.A.shape[0]
283 num_inputs = self.B.shape[1]
284 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700285 ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
286 num_states, num_inputs, num_outputs, self.ControllerFunction())]
Austin Schuh3c542312013-02-24 01:53:50 -0800287
Austin Schuhe3490622013-03-13 01:24:30 -0700288 ans.append(self._DumpMatrix('L', self.L))
289 ans.append(self._DumpMatrix('K', self.K))
Austin Schuh3c542312013-02-24 01:53:50 -0800290
Austin Schuhe3490622013-03-13 01:24:30 -0700291 ans.append(' return StateFeedbackController<%d, %d, %d>'
292 '(L, K, Make%sPlantCoefficients());\n' % (num_states, num_inputs,
293 num_outputs, self._name))
294 ans.append('}\n')
295 return ''.join(ans)