blob: 4b63aec04e4d81c6f91eb7fdfaf9bcbb3d041a88 [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
65 def WriteHeader(self, header_file):
66 """Writes the header file to the file named header_file."""
67 with open(header_file, 'w') as fd:
68 header_guard = self._HeaderGuard(header_file)
69 fd.write('#ifndef %s\n'
70 '#define %s\n\n' % (header_guard, header_guard))
71 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
72 fd.write('\n')
73
74 fd.write(self._namespace_start)
75 fd.write('\n\n')
76 for loop in self._loops:
77 fd.write(loop.DumpPlantHeader())
78 fd.write('\n')
79 fd.write(loop.DumpControllerHeader())
80 fd.write('\n')
81
82 fd.write('%s Make%sPlant();\n\n' %
83 (self._PlantType(), self._gain_schedule_name))
84
85 fd.write('%s Make%sLoop();\n\n' %
86 (self._LoopType(), self._gain_schedule_name))
87
88 fd.write(self._namespace_end)
89 fd.write('\n\n')
90 fd.write("#endif // %s\n" % header_guard)
91
92 def WriteCC(self, header_file_name, cc_file):
93 """Writes the cc file to the file named cc_file."""
94 with open(cc_file, 'w') as fd:
Brian Silvermandf3e7b22013-11-08 19:43:27 -080095 fd.write('#include \"%s/control_loops/%s\"\n' %
Brian Silvermane51ad632014-01-08 15:12:29 -080096 (self._TopDirectory(), header_file_name))
Austin Schuhe3490622013-03-13 01:24:30 -070097 fd.write('\n')
98 fd.write('#include <vector>\n')
99 fd.write('\n')
100 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
101 fd.write('\n')
102 fd.write(self._namespace_start)
103 fd.write('\n\n')
104 for loop in self._loops:
105 fd.write(loop.DumpPlant())
106 fd.write('\n')
107
108 for loop in self._loops:
109 fd.write(loop.DumpController())
110 fd.write('\n')
111
112 fd.write('%s Make%sPlant() {\n' %
113 (self._PlantType(), self._gain_schedule_name))
114 fd.write(' ::std::vector<%s *> plants(%d);\n' % (
115 self._CoeffType(), len(self._loops)))
116 for index, loop in enumerate(self._loops):
117 fd.write(' plants[%d] = new %s(%s);\n' %
118 (index, self._CoeffType(),
119 loop.PlantFunction()))
120 fd.write(' return %s(plants);\n' % self._PlantType())
121 fd.write('}\n\n')
122
123 fd.write('%s Make%sLoop() {\n' %
124 (self._LoopType(), self._gain_schedule_name))
125 fd.write(' ::std::vector<%s *> controllers(%d);\n' % (
126 self._ControllerType(), len(self._loops)))
127 for index, loop in enumerate(self._loops):
128 fd.write(' controllers[%d] = new %s(%s);\n' %
129 (index, self._ControllerType(),
130 loop.ControllerFunction()))
131 fd.write(' return %s(controllers);\n' % self._LoopType())
132 fd.write('}\n\n')
133
134 fd.write(self._namespace_end)
135 fd.write('\n')
136
137
Austin Schuh3c542312013-02-24 01:53:50 -0800138class ControlLoop(object):
139 def __init__(self, name):
140 """Constructs a control loop object.
141
142 Args:
143 name: string, The name of the loop to use when writing the C++ files.
144 """
145 self._name = name
146
Austin Schuhc1f68892013-03-16 17:06:27 -0700147 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt):
148 """Calculates the discrete time values for A and B.
Austin Schuh3c542312013-02-24 01:53:50 -0800149
150 Args:
151 A_continuous: numpy.matrix, The continuous time A matrix
152 B_continuous: numpy.matrix, The continuous time B matrix
153 dt: float, The time step of the control loop
Austin Schuhc1f68892013-03-16 17:06:27 -0700154
155 Returns:
156 (A, B), numpy.matrix, the control matricies.
Austin Schuh3c542312013-02-24 01:53:50 -0800157 """
Austin Schuhc1f68892013-03-16 17:06:27 -0700158 return controls.c2d(A_continuous, B_continuous, dt)
159
160 def InitializeState(self):
161 """Sets X, Y, and X_hat to zero defaults."""
Austin Schuh3c542312013-02-24 01:53:50 -0800162 self.X = numpy.zeros((self.A.shape[0], 1))
Austin Schuhc1f68892013-03-16 17:06:27 -0700163 self.Y = self.C * self.X
Austin Schuh3c542312013-02-24 01:53:50 -0800164 self.X_hat = numpy.zeros((self.A.shape[0], 1))
165
166 def PlaceControllerPoles(self, poles):
167 """Places the controller poles.
168
169 Args:
170 poles: array, An array of poles. Must be complex conjegates if they have
171 any imaginary portions.
172 """
173 self.K = controls.dplace(self.A, self.B, poles)
174
175 def PlaceObserverPoles(self, poles):
176 """Places the observer poles.
177
178 Args:
179 poles: array, An array of poles. Must be complex conjegates if they have
180 any imaginary portions.
181 """
182 self.L = controls.dplace(self.A.T, self.C.T, poles).T
183
184 def Update(self, U):
185 """Simulates one time step with the provided U."""
186 U = numpy.clip(U, self.U_min, self.U_max)
187 self.X = self.A * self.X + self.B * U
188 self.Y = self.C * self.X + self.D * U
189
190 def UpdateObserver(self, U):
191 """Updates the observer given the provided U."""
192 self.X_hat = (self.A * self.X_hat + self.B * U +
193 self.L * (self.Y - self.C * self.X_hat - self.D * U))
194
195 def _DumpMatrix(self, matrix_name, matrix):
196 """Dumps the provided matrix into a variable called matrix_name.
197
198 Args:
199 matrix_name: string, The variable name to save the matrix to.
200 matrix: The matrix to dump.
201
202 Returns:
203 string, The C++ commands required to populate a variable named matrix_name
204 with the contents of matrix.
205 """
Austin Schuhe3490622013-03-13 01:24:30 -0700206 ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
Austin Schuh3c542312013-02-24 01:53:50 -0800207 matrix.shape[0], matrix.shape[1], matrix_name)]
208 first = True
Brian Silverman0f637382013-03-03 17:44:46 -0800209 for x in xrange(matrix.shape[0]):
210 for y in xrange(matrix.shape[1]):
Austin Schuh7ec34fd2014-02-15 22:27:46 -0800211 element = matrix[x, y]
Brian Silverman0f637382013-03-03 17:44:46 -0800212 if first:
Austin Schuhe3490622013-03-13 01:24:30 -0700213 ans.append(' %s << ' % matrix_name)
Brian Silverman0f637382013-03-03 17:44:46 -0800214 first = False
215 else:
Austin Schuhe3490622013-03-13 01:24:30 -0700216 ans.append(', ')
Brian Silverman0f637382013-03-03 17:44:46 -0800217 ans.append(str(element))
Austin Schuh3c542312013-02-24 01:53:50 -0800218
Austin Schuhe3490622013-03-13 01:24:30 -0700219 ans.append(';\n')
220 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800221
Austin Schuhe3490622013-03-13 01:24:30 -0700222 def DumpPlantHeader(self):
Austin Schuh3c542312013-02-24 01:53:50 -0800223 """Writes out a c++ header declaration which will create a Plant object.
224
Austin Schuh3c542312013-02-24 01:53:50 -0800225 Returns:
226 string, The header declaration for the function.
227 """
228 num_states = self.A.shape[0]
229 num_inputs = self.B.shape[1]
230 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700231 return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
232 num_states, num_inputs, num_outputs, self._name)
Austin Schuh3c542312013-02-24 01:53:50 -0800233
Austin Schuhe3490622013-03-13 01:24:30 -0700234 def DumpPlant(self):
235 """Writes out a c++ function which will create a PlantCoefficients object.
Austin Schuh3c542312013-02-24 01:53:50 -0800236
237 Returns:
238 string, The function which will create the object.
239 """
240 num_states = self.A.shape[0]
241 num_inputs = self.B.shape[1]
242 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700243 ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
244 ' Make%sPlantCoefficients() {\n' % (
245 num_states, num_inputs, num_outputs, self._name)]
Austin Schuh3c542312013-02-24 01:53:50 -0800246
Austin Schuhe3490622013-03-13 01:24:30 -0700247 ans.append(self._DumpMatrix('A', self.A))
248 ans.append(self._DumpMatrix('B', self.B))
249 ans.append(self._DumpMatrix('C', self.C))
250 ans.append(self._DumpMatrix('D', self.D))
251 ans.append(self._DumpMatrix('U_max', self.U_max))
252 ans.append(self._DumpMatrix('U_min', self.U_min))
Austin Schuh3c542312013-02-24 01:53:50 -0800253
Austin Schuhe3490622013-03-13 01:24:30 -0700254 ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
255 '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
Austin Schuh3c542312013-02-24 01:53:50 -0800256 num_outputs))
Austin Schuhe3490622013-03-13 01:24:30 -0700257 ans.append('}\n')
258 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800259
Austin Schuhe3490622013-03-13 01:24:30 -0700260 def PlantFunction(self):
261 """Returns the name of the plant coefficient function."""
262 return 'Make%sPlantCoefficients()' % self._name
Austin Schuh3c542312013-02-24 01:53:50 -0800263
Austin Schuhe3490622013-03-13 01:24:30 -0700264 def ControllerFunction(self):
265 """Returns the name of the controller function."""
266 return 'Make%sController()' % self._name
267
268 def DumpControllerHeader(self):
269 """Writes out a c++ header declaration which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800270
271 Returns:
272 string, The header declaration for the function.
273 """
274 num_states = self.A.shape[0]
275 num_inputs = self.B.shape[1]
276 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700277 return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
278 num_states, num_inputs, num_outputs, self.ControllerFunction())
Austin Schuh3c542312013-02-24 01:53:50 -0800279
Austin Schuhe3490622013-03-13 01:24:30 -0700280 def DumpController(self):
281 """Returns a c++ function which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800282
283 Returns:
284 string, The function which will create the object.
285 """
286 num_states = self.A.shape[0]
287 num_inputs = self.B.shape[1]
288 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700289 ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
290 num_states, num_inputs, num_outputs, self.ControllerFunction())]
Austin Schuh3c542312013-02-24 01:53:50 -0800291
Austin Schuhe3490622013-03-13 01:24:30 -0700292 ans.append(self._DumpMatrix('L', self.L))
293 ans.append(self._DumpMatrix('K', self.K))
Austin Schuh3c542312013-02-24 01:53:50 -0800294
Austin Schuhe3490622013-03-13 01:24:30 -0700295 ans.append(' return StateFeedbackController<%d, %d, %d>'
296 '(L, K, Make%sPlantCoefficients());\n' % (num_states, num_inputs,
297 num_outputs, self._name))
298 ans.append('}\n')
299 return ''.join(ans)