blob: 4ff262309c64d01cfb9978810fcab4104533cbee [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 Schuh3c542312013-02-24 01:53:50 -0800143 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt, C):
144 """Calculates the discrete time values for A and B as well as initializing
145 X and Y to the correct sizes.
146
147 Args:
148 A_continuous: numpy.matrix, The continuous time A matrix
149 B_continuous: numpy.matrix, The continuous time B matrix
150 dt: float, The time step of the control loop
151 C: C
152 """
153 self.A, self.B = controls.c2d(
154 A_continuous, B_continuous, dt)
155 self.X = numpy.zeros((self.A.shape[0], 1))
156 self.Y = C * self.X
157 self.X_hat = numpy.zeros((self.A.shape[0], 1))
158
159 def PlaceControllerPoles(self, poles):
160 """Places the controller poles.
161
162 Args:
163 poles: array, An array of poles. Must be complex conjegates if they have
164 any imaginary portions.
165 """
166 self.K = controls.dplace(self.A, self.B, poles)
167
168 def PlaceObserverPoles(self, poles):
169 """Places the observer poles.
170
171 Args:
172 poles: array, An array of poles. Must be complex conjegates if they have
173 any imaginary portions.
174 """
175 self.L = controls.dplace(self.A.T, self.C.T, poles).T
176
177 def Update(self, U):
178 """Simulates one time step with the provided U."""
179 U = numpy.clip(U, self.U_min, self.U_max)
180 self.X = self.A * self.X + self.B * U
181 self.Y = self.C * self.X + self.D * U
182
183 def UpdateObserver(self, U):
184 """Updates the observer given the provided U."""
185 self.X_hat = (self.A * self.X_hat + self.B * U +
186 self.L * (self.Y - self.C * self.X_hat - self.D * U))
187
188 def _DumpMatrix(self, matrix_name, matrix):
189 """Dumps the provided matrix into a variable called matrix_name.
190
191 Args:
192 matrix_name: string, The variable name to save the matrix to.
193 matrix: The matrix to dump.
194
195 Returns:
196 string, The C++ commands required to populate a variable named matrix_name
197 with the contents of matrix.
198 """
Austin Schuhe3490622013-03-13 01:24:30 -0700199 ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
Austin Schuh3c542312013-02-24 01:53:50 -0800200 matrix.shape[0], matrix.shape[1], matrix_name)]
201 first = True
Brian Silverman0f637382013-03-03 17:44:46 -0800202 for x in xrange(matrix.shape[0]):
203 for y in xrange(matrix.shape[1]):
204 element = matrix[x, y]
205 if first:
Austin Schuhe3490622013-03-13 01:24:30 -0700206 ans.append(' %s << ' % matrix_name)
Brian Silverman0f637382013-03-03 17:44:46 -0800207 first = False
208 else:
Austin Schuhe3490622013-03-13 01:24:30 -0700209 ans.append(', ')
Brian Silverman0f637382013-03-03 17:44:46 -0800210 ans.append(str(element))
Austin Schuh3c542312013-02-24 01:53:50 -0800211
Austin Schuhe3490622013-03-13 01:24:30 -0700212 ans.append(';\n')
213 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800214
Austin Schuhe3490622013-03-13 01:24:30 -0700215 def DumpPlantHeader(self):
Austin Schuh3c542312013-02-24 01:53:50 -0800216 """Writes out a c++ header declaration which will create a Plant object.
217
Austin Schuh3c542312013-02-24 01:53:50 -0800218 Returns:
219 string, The header declaration for the function.
220 """
221 num_states = self.A.shape[0]
222 num_inputs = self.B.shape[1]
223 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700224 return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
225 num_states, num_inputs, num_outputs, self._name)
Austin Schuh3c542312013-02-24 01:53:50 -0800226
Austin Schuhe3490622013-03-13 01:24:30 -0700227 def DumpPlant(self):
228 """Writes out a c++ function which will create a PlantCoefficients object.
Austin Schuh3c542312013-02-24 01:53:50 -0800229
230 Returns:
231 string, The function which will create the object.
232 """
233 num_states = self.A.shape[0]
234 num_inputs = self.B.shape[1]
235 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700236 ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
237 ' 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 ans.append(self._DumpMatrix('A', self.A))
241 ans.append(self._DumpMatrix('B', self.B))
242 ans.append(self._DumpMatrix('C', self.C))
243 ans.append(self._DumpMatrix('D', self.D))
244 ans.append(self._DumpMatrix('U_max', self.U_max))
245 ans.append(self._DumpMatrix('U_min', self.U_min))
Austin Schuh3c542312013-02-24 01:53:50 -0800246
Austin Schuhe3490622013-03-13 01:24:30 -0700247 ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
248 '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
Austin Schuh3c542312013-02-24 01:53:50 -0800249 num_outputs))
Austin Schuhe3490622013-03-13 01:24:30 -0700250 ans.append('}\n')
251 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800252
Austin Schuhe3490622013-03-13 01:24:30 -0700253 def PlantFunction(self):
254 """Returns the name of the plant coefficient function."""
255 return 'Make%sPlantCoefficients()' % self._name
Austin Schuh3c542312013-02-24 01:53:50 -0800256
Austin Schuhe3490622013-03-13 01:24:30 -0700257 def ControllerFunction(self):
258 """Returns the name of the controller function."""
259 return 'Make%sController()' % self._name
260
261 def DumpControllerHeader(self):
262 """Writes out a c++ header declaration which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800263
264 Returns:
265 string, The header declaration for the function.
266 """
267 num_states = self.A.shape[0]
268 num_inputs = self.B.shape[1]
269 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700270 return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
271 num_states, num_inputs, num_outputs, self.ControllerFunction())
Austin Schuh3c542312013-02-24 01:53:50 -0800272
Austin Schuhe3490622013-03-13 01:24:30 -0700273 def DumpController(self):
274 """Returns a c++ function which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800275
276 Returns:
277 string, The function which will create the object.
278 """
279 num_states = self.A.shape[0]
280 num_inputs = self.B.shape[1]
281 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700282 ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
283 num_states, num_inputs, num_outputs, self.ControllerFunction())]
Austin Schuh3c542312013-02-24 01:53:50 -0800284
Austin Schuhe3490622013-03-13 01:24:30 -0700285 ans.append(self._DumpMatrix('L', self.L))
286 ans.append(self._DumpMatrix('K', self.K))
Austin Schuh3c542312013-02-24 01:53:50 -0800287
Austin Schuhe3490622013-03-13 01:24:30 -0700288 ans.append(' return StateFeedbackController<%d, %d, %d>'
289 '(L, K, Make%sPlantCoefficients());\n' % (num_states, num_inputs,
290 num_outputs, self._name))
291 ans.append('}\n')
292 return ''.join(ans)