blob: bc460cf39a3b538dff59c248cd814e84a462b7ae [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:
James Kuszmauladab8f92013-11-06 08:42:34 -080020 self._namespaces = ['frc971', 'control_loops']
Austin Schuhe3490622013-03-13 01:24:30 -070021
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):
James Kuszmaula0850442013-11-06 18:27:20 -080029 return (self._namespaces[0].upper() + '_CONTROL_LOOPS_' +
Austin Schuhe3490622013-03-13 01:24:30 -070030 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:
Brian Silvermandf3e7b22013-11-08 19:43:27 -080092 fd.write('#include \"%s/control_loops/%s\"\n' %
93 (self._namespaces[0] + header_file_name))
Austin Schuhe3490622013-03-13 01:24:30 -070094 fd.write('\n')
95 fd.write('#include <vector>\n')
96 fd.write('\n')
97 fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
98 fd.write('\n')
99 fd.write(self._namespace_start)
100 fd.write('\n\n')
101 for loop in self._loops:
102 fd.write(loop.DumpPlant())
103 fd.write('\n')
104
105 for loop in self._loops:
106 fd.write(loop.DumpController())
107 fd.write('\n')
108
109 fd.write('%s Make%sPlant() {\n' %
110 (self._PlantType(), self._gain_schedule_name))
111 fd.write(' ::std::vector<%s *> plants(%d);\n' % (
112 self._CoeffType(), len(self._loops)))
113 for index, loop in enumerate(self._loops):
114 fd.write(' plants[%d] = new %s(%s);\n' %
115 (index, self._CoeffType(),
116 loop.PlantFunction()))
117 fd.write(' return %s(plants);\n' % self._PlantType())
118 fd.write('}\n\n')
119
120 fd.write('%s Make%sLoop() {\n' %
121 (self._LoopType(), self._gain_schedule_name))
122 fd.write(' ::std::vector<%s *> controllers(%d);\n' % (
123 self._ControllerType(), len(self._loops)))
124 for index, loop in enumerate(self._loops):
125 fd.write(' controllers[%d] = new %s(%s);\n' %
126 (index, self._ControllerType(),
127 loop.ControllerFunction()))
128 fd.write(' return %s(controllers);\n' % self._LoopType())
129 fd.write('}\n\n')
130
131 fd.write(self._namespace_end)
132 fd.write('\n')
133
134
Austin Schuh3c542312013-02-24 01:53:50 -0800135class ControlLoop(object):
136 def __init__(self, name):
137 """Constructs a control loop object.
138
139 Args:
140 name: string, The name of the loop to use when writing the C++ files.
141 """
142 self._name = name
143
Austin Schuhc1f68892013-03-16 17:06:27 -0700144 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt):
145 """Calculates the discrete time values for A and B.
Austin Schuh3c542312013-02-24 01:53:50 -0800146
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
Austin Schuhc1f68892013-03-16 17:06:27 -0700151
152 Returns:
153 (A, B), numpy.matrix, the control matricies.
Austin Schuh3c542312013-02-24 01:53:50 -0800154 """
Austin Schuhc1f68892013-03-16 17:06:27 -0700155 return controls.c2d(A_continuous, B_continuous, dt)
156
157 def InitializeState(self):
158 """Sets X, Y, and X_hat to zero defaults."""
Austin Schuh3c542312013-02-24 01:53:50 -0800159 self.X = numpy.zeros((self.A.shape[0], 1))
Austin Schuhc1f68892013-03-16 17:06:27 -0700160 self.Y = self.C * self.X
Austin Schuh3c542312013-02-24 01:53:50 -0800161 self.X_hat = numpy.zeros((self.A.shape[0], 1))
162
163 def PlaceControllerPoles(self, poles):
164 """Places the controller poles.
165
166 Args:
167 poles: array, An array of poles. Must be complex conjegates if they have
168 any imaginary portions.
169 """
170 self.K = controls.dplace(self.A, self.B, poles)
171
172 def PlaceObserverPoles(self, poles):
173 """Places the observer 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.L = controls.dplace(self.A.T, self.C.T, poles).T
180
181 def Update(self, U):
182 """Simulates one time step with the provided U."""
183 U = numpy.clip(U, self.U_min, self.U_max)
184 self.X = self.A * self.X + self.B * U
185 self.Y = self.C * self.X + self.D * U
186
187 def UpdateObserver(self, U):
188 """Updates the observer given the provided U."""
189 self.X_hat = (self.A * self.X_hat + self.B * U +
190 self.L * (self.Y - self.C * self.X_hat - self.D * U))
191
192 def _DumpMatrix(self, matrix_name, matrix):
193 """Dumps the provided matrix into a variable called matrix_name.
194
195 Args:
196 matrix_name: string, The variable name to save the matrix to.
197 matrix: The matrix to dump.
198
199 Returns:
200 string, The C++ commands required to populate a variable named matrix_name
201 with the contents of matrix.
202 """
Austin Schuhe3490622013-03-13 01:24:30 -0700203 ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
Austin Schuh3c542312013-02-24 01:53:50 -0800204 matrix.shape[0], matrix.shape[1], matrix_name)]
205 first = True
Brian Silverman0f637382013-03-03 17:44:46 -0800206 for x in xrange(matrix.shape[0]):
207 for y in xrange(matrix.shape[1]):
208 element = matrix[x, y]
209 if first:
Austin Schuhe3490622013-03-13 01:24:30 -0700210 ans.append(' %s << ' % matrix_name)
Brian Silverman0f637382013-03-03 17:44:46 -0800211 first = False
212 else:
Austin Schuhe3490622013-03-13 01:24:30 -0700213 ans.append(', ')
Brian Silverman0f637382013-03-03 17:44:46 -0800214 ans.append(str(element))
Austin Schuh3c542312013-02-24 01:53:50 -0800215
Austin Schuhe3490622013-03-13 01:24:30 -0700216 ans.append(';\n')
217 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800218
Austin Schuhe3490622013-03-13 01:24:30 -0700219 def DumpPlantHeader(self):
Austin Schuh3c542312013-02-24 01:53:50 -0800220 """Writes out a c++ header declaration which will create a Plant object.
221
Austin Schuh3c542312013-02-24 01:53:50 -0800222 Returns:
223 string, The header declaration for the function.
224 """
225 num_states = self.A.shape[0]
226 num_inputs = self.B.shape[1]
227 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700228 return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
229 num_states, num_inputs, num_outputs, self._name)
Austin Schuh3c542312013-02-24 01:53:50 -0800230
Austin Schuhe3490622013-03-13 01:24:30 -0700231 def DumpPlant(self):
232 """Writes out a c++ function which will create a PlantCoefficients object.
Austin Schuh3c542312013-02-24 01:53:50 -0800233
234 Returns:
235 string, The function which will create the object.
236 """
237 num_states = self.A.shape[0]
238 num_inputs = self.B.shape[1]
239 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700240 ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
241 ' Make%sPlantCoefficients() {\n' % (
242 num_states, num_inputs, num_outputs, self._name)]
Austin Schuh3c542312013-02-24 01:53:50 -0800243
Austin Schuhe3490622013-03-13 01:24:30 -0700244 ans.append(self._DumpMatrix('A', self.A))
245 ans.append(self._DumpMatrix('B', self.B))
246 ans.append(self._DumpMatrix('C', self.C))
247 ans.append(self._DumpMatrix('D', self.D))
248 ans.append(self._DumpMatrix('U_max', self.U_max))
249 ans.append(self._DumpMatrix('U_min', self.U_min))
Austin Schuh3c542312013-02-24 01:53:50 -0800250
Austin Schuhe3490622013-03-13 01:24:30 -0700251 ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
252 '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
Austin Schuh3c542312013-02-24 01:53:50 -0800253 num_outputs))
Austin Schuhe3490622013-03-13 01:24:30 -0700254 ans.append('}\n')
255 return ''.join(ans)
Austin Schuh3c542312013-02-24 01:53:50 -0800256
Austin Schuhe3490622013-03-13 01:24:30 -0700257 def PlantFunction(self):
258 """Returns the name of the plant coefficient function."""
259 return 'Make%sPlantCoefficients()' % self._name
Austin Schuh3c542312013-02-24 01:53:50 -0800260
Austin Schuhe3490622013-03-13 01:24:30 -0700261 def ControllerFunction(self):
262 """Returns the name of the controller function."""
263 return 'Make%sController()' % self._name
264
265 def DumpControllerHeader(self):
266 """Writes out a c++ header declaration which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800267
268 Returns:
269 string, The header declaration for the function.
270 """
271 num_states = self.A.shape[0]
272 num_inputs = self.B.shape[1]
273 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700274 return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
275 num_states, num_inputs, num_outputs, self.ControllerFunction())
Austin Schuh3c542312013-02-24 01:53:50 -0800276
Austin Schuhe3490622013-03-13 01:24:30 -0700277 def DumpController(self):
278 """Returns a c++ function which will create a Controller object.
Austin Schuh3c542312013-02-24 01:53:50 -0800279
280 Returns:
281 string, The function which will create the object.
282 """
283 num_states = self.A.shape[0]
284 num_inputs = self.B.shape[1]
285 num_outputs = self.C.shape[0]
Austin Schuhe3490622013-03-13 01:24:30 -0700286 ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
287 num_states, num_inputs, num_outputs, self.ControllerFunction())]
Austin Schuh3c542312013-02-24 01:53:50 -0800288
Austin Schuhe3490622013-03-13 01:24:30 -0700289 ans.append(self._DumpMatrix('L', self.L))
290 ans.append(self._DumpMatrix('K', self.K))
Austin Schuh3c542312013-02-24 01:53:50 -0800291
Austin Schuhe3490622013-03-13 01:24:30 -0700292 ans.append(' return StateFeedbackController<%d, %d, %d>'
293 '(L, K, Make%sPlantCoefficients());\n' % (num_states, num_inputs,
294 num_outputs, self._name))
295 ans.append('}\n')
296 return ''.join(ans)