blob: 47550a1901d16d92503398d27d434d71df2b57f8 [file] [log] [blame]
Austin Schuh3c542312013-02-24 01:53:50 -08001import controls
2import numpy
3
4class ControlLoop(object):
5 def __init__(self, name):
6 """Constructs a control loop object.
7
8 Args:
9 name: string, The name of the loop to use when writing the C++ files.
10 """
11 self._name = name
12
13 self._namespace_start = ("namespace frc971 {\n"
14 "namespace control_loops {\n\n")
15
16 self._namespace_end = ("} // namespace frc971\n"
17 "} // namespace control_loops\n")
18
19 self._header_start = ("#ifndef FRC971_CONTROL_LOOPS_%s_MOTOR_PLANT_H_\n"
20 "#define FRC971_CONTROL_LOOPS_%s_MOTOR_PLANT_H_\n\n"
21 % (self._name.upper(), self._name.upper()))
22
23 self._header_end = ("#endif // FRC971_CONTROL_LOOPS_%s_MOTOR_PLANT_H_\n"
24 % (self._name.upper()))
25
26 def ContinuousToDiscrete(self, A_continuous, B_continuous, dt, C):
27 """Calculates the discrete time values for A and B as well as initializing
28 X and Y to the correct sizes.
29
30 Args:
31 A_continuous: numpy.matrix, The continuous time A matrix
32 B_continuous: numpy.matrix, The continuous time B matrix
33 dt: float, The time step of the control loop
34 C: C
35 """
36 self.A, self.B = controls.c2d(
37 A_continuous, B_continuous, dt)
38 self.X = numpy.zeros((self.A.shape[0], 1))
39 self.Y = C * self.X
40 self.X_hat = numpy.zeros((self.A.shape[0], 1))
41
42 def PlaceControllerPoles(self, poles):
43 """Places the controller poles.
44
45 Args:
46 poles: array, An array of poles. Must be complex conjegates if they have
47 any imaginary portions.
48 """
49 self.K = controls.dplace(self.A, self.B, poles)
50
51 def PlaceObserverPoles(self, poles):
52 """Places the observer poles.
53
54 Args:
55 poles: array, An array of poles. Must be complex conjegates if they have
56 any imaginary portions.
57 """
58 self.L = controls.dplace(self.A.T, self.C.T, poles).T
59
60 def Update(self, U):
61 """Simulates one time step with the provided U."""
62 U = numpy.clip(U, self.U_min, self.U_max)
63 self.X = self.A * self.X + self.B * U
64 self.Y = self.C * self.X + self.D * U
65
66 def UpdateObserver(self, U):
67 """Updates the observer given the provided U."""
68 self.X_hat = (self.A * self.X_hat + self.B * U +
69 self.L * (self.Y - self.C * self.X_hat - self.D * U))
70
71 def _DumpMatrix(self, matrix_name, matrix):
72 """Dumps the provided matrix into a variable called matrix_name.
73
74 Args:
75 matrix_name: string, The variable name to save the matrix to.
76 matrix: The matrix to dump.
77
78 Returns:
79 string, The C++ commands required to populate a variable named matrix_name
80 with the contents of matrix.
81 """
82 ans = [" Eigen::Matrix<double, %d, %d> %s;\n" % (
83 matrix.shape[0], matrix.shape[1], matrix_name)]
84 first = True
85 for element in numpy.nditer(matrix, order='C'):
86 if first:
87 ans.append(" %s << " % matrix_name)
88 first = False
89 else:
90 ans.append(", ")
91 ans.append(str(element))
92
93 ans.append(";\n")
94 return "".join(ans)
95
96 def _DumpPlantHeader(self, plant_name):
97 """Writes out a c++ header declaration which will create a Plant object.
98
99 Args:
100 plant_name: string, the name of the plant. Used to create the name of the
101 function. The function name will be Make<plant_name>Plant().
102
103 Returns:
104 string, The header declaration for the function.
105 """
106 num_states = self.A.shape[0]
107 num_inputs = self.B.shape[1]
108 num_outputs = self.C.shape[0]
109 return "StateFeedbackPlant<%d, %d, %d> Make%sPlant();\n" % (
110 num_states, num_inputs, num_outputs, plant_name)
111
112 def _DumpPlant(self, plant_name):
113 """Writes out a c++ function which will create a Plant object.
114
115 Args:
116 plant_name: string, the name of the plant. Used to create the name of the
117 function. The function name will be Make<plant_name>Plant().
118
119 Returns:
120 string, The function which will create the object.
121 """
122 num_states = self.A.shape[0]
123 num_inputs = self.B.shape[1]
124 num_outputs = self.C.shape[0]
125 ans = ["StateFeedbackPlant<%d, %d, %d> Make%sPlant() {\n" % (
126 num_states, num_inputs, num_outputs, plant_name)]
127
128 ans.append(self._DumpMatrix("A", self.A))
129 ans.append(self._DumpMatrix("B", self.B))
130 ans.append(self._DumpMatrix("C", self.C))
131 ans.append(self._DumpMatrix("D", self.D))
132 ans.append(self._DumpMatrix("U_max", self.U_max))
133 ans.append(self._DumpMatrix("U_min", self.U_min))
134
135 ans.append(" return StateFeedbackPlant<%d, %d, %d>"
136 "(A, B, C, D, U_max, U_min);\n" % (num_states, num_inputs,
137 num_outputs))
138 ans.append("}\n")
139 return "".join(ans)
140
141 def _DumpLoopHeader(self, loop_name):
142 """Writes out a c++ header declaration which will create a Loop object.
143
144 Args:
145 loop_name: string, the name of the loop. Used to create the name of the
146 function. The function name will be Make<loop_name>Loop().
147
148 Returns:
149 string, The header declaration for the function.
150 """
151 num_states = self.A.shape[0]
152 num_inputs = self.B.shape[1]
153 num_outputs = self.C.shape[0]
154 return "StateFeedbackLoop<%d, %d, %d> Make%sLoop();\n" % (
155 num_states, num_inputs, num_outputs, loop_name)
156
157 def _DumpLoop(self, loop_name):
158 """Returns a c++ function which will create a Loop object.
159
160 Args:
161 loop_name: string, the name of the loop. Used to create the name of the
162 function and create the plant. The function name will be
163 Make<loop_name>Loop().
164
165 Returns:
166 string, The function which will create the object.
167 """
168 num_states = self.A.shape[0]
169 num_inputs = self.B.shape[1]
170 num_outputs = self.C.shape[0]
171 ans = ["StateFeedbackLoop<%d, %d, %d> Make%sLoop() {\n" % (
172 num_states, num_inputs, num_outputs, loop_name)]
173
174 ans.append(self._DumpMatrix("L", self.L))
175 ans.append(self._DumpMatrix("K", self.K))
176
177 ans.append(" return StateFeedbackLoop<%d, %d, %d>"
178 "(L, K, Make%sPlant());\n" % (num_states, num_inputs,
179 num_outputs, loop_name))
180 ans.append("}\n")
181 return "".join(ans)
182
183 def DumpHeaderFile(self, file_name):
184 """Writes the header file for creating a Plant and Loop object.
185
186 Args:
187 file_name: string, name of the file to write the header file to.
188 """
189 with open(file_name, "w") as fd:
190 fd.write(self._header_start)
191 fd.write("#include \"frc971/control_loops/state_feedback_loop.h\"\n")
192 fd.write('\n')
193 fd.write(self._namespace_start)
194 fd.write(self._DumpPlantHeader(self._name))
195 fd.write('\n')
196 fd.write(self._DumpLoopHeader("Wrist"))
197 fd.write('\n')
198 fd.write(self._namespace_end)
199 fd.write('\n')
200 fd.write(self._header_end)
201
202 def DumpCppFile(self, file_name, header_file_name):
203 """Writes the C++ file for creating a Plant and Loop object.
204
205 Args:
206 file_name: string, name of the file to write the header file to.
207 """
208 with open(file_name, "w") as fd:
209 fd.write("#include \"frc971/control_loops/%s\"\n" % header_file_name)
210 fd.write('\n')
211 fd.write("#include \"frc971/control_loops/state_feedback_loop.h\"\n")
212 fd.write('\n')
213 fd.write(self._namespace_start)
214 fd.write('\n')
215 fd.write(self._DumpPlant(self._name))
216 fd.write('\n')
217 fd.write(self._DumpLoop(self._name))
218 fd.write('\n')
219 fd.write(self._namespace_end)