blob: aa8251ea747a0b242a2e842f60eb011a636017ff [file] [log] [blame]
Austin Schuh085eab92020-11-26 13:54:51 -08001#!/usr/bin/python3
Austin Schuhb5d302f2019-01-20 20:51:19 -08002
3from aos.util.trapezoid_profile import TrapezoidProfile
4from frc971.control_loops.python import control_loop
5from frc971.control_loops.python import controls
6import numpy
Austin Schuhb5d302f2019-01-20 20:51:19 -08007from matplotlib import pylab
8import glog
9
10
11class LinearSystemParams(object):
Ravago Jones5127ccc2022-07-31 16:32:45 -070012
Austin Schuhb5d302f2019-01-20 20:51:19 -080013 def __init__(self,
14 name,
15 motor,
16 G,
17 radius,
18 mass,
19 q_pos,
20 q_vel,
21 kalman_q_pos,
22 kalman_q_vel,
23 kalman_q_voltage,
24 kalman_r_position,
Austin Schuh63d095d2019-02-23 11:57:12 -080025 dt=0.00505):
Austin Schuhb5d302f2019-01-20 20:51:19 -080026 self.name = name
27 self.motor = motor
28 self.G = G
29 self.radius = radius
30 self.mass = mass
31 self.q_pos = q_pos
32 self.q_vel = q_vel
33 self.kalman_q_pos = kalman_q_pos
34 self.kalman_q_vel = kalman_q_vel
35 self.kalman_q_voltage = kalman_q_voltage
36 self.kalman_r_position = kalman_r_position
37 self.dt = dt
38
39
40class LinearSystem(control_loop.ControlLoop):
Ravago Jones5127ccc2022-07-31 16:32:45 -070041
Austin Schuhb5d302f2019-01-20 20:51:19 -080042 def __init__(self, params, name='LinearSystem'):
43 super(LinearSystem, self).__init__(name)
44 self.params = params
45
46 self.motor = params.motor
47
48 # Gear ratio
49 self.G = params.G
50 self.radius = params.radius
51
Austin Schuh2e554032019-01-21 15:07:27 -080052 # Mass in kg
Austin Schuh2e282d12024-02-19 12:00:58 -080053 self.mass_motor = self.motor.motor_inertia / (
Austin Schuhb5d302f2019-01-20 20:51:19 -080054 (self.G * self.radius)**2.0)
Austin Schuh2e282d12024-02-19 12:00:58 -080055 self.mass = params.mass + self.mass_motor
Austin Schuhb5d302f2019-01-20 20:51:19 -080056
57 # Control loop time step
58 self.dt = params.dt
59
60 # State is [position, velocity]
61 # Input is [Voltage]
Ravago Jones5127ccc2022-07-31 16:32:45 -070062 C1 = self.motor.Kt / (self.G * self.G * self.radius * self.radius *
63 self.motor.resistance * self.mass *
64 self.motor.Kv)
65 C2 = self.motor.Kt / (self.G * self.radius * self.motor.resistance *
66 self.mass)
Austin Schuhb5d302f2019-01-20 20:51:19 -080067
68 self.A_continuous = numpy.matrix([[0, 1], [0, -C1]])
69
70 # Start with the unmodified input
71 self.B_continuous = numpy.matrix([[0], [C2]])
72 glog.debug(repr(self.A_continuous))
73 glog.debug(repr(self.B_continuous))
74
75 self.C = numpy.matrix([[1, 0]])
76 self.D = numpy.matrix([[0]])
77
78 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
79 self.B_continuous, self.dt)
80
81 controllability = controls.ctrb(self.A, self.B)
82 glog.debug('Controllability of %d',
83 numpy.linalg.matrix_rank(controllability))
Austin Schuh2e282d12024-02-19 12:00:58 -080084 glog.debug('Mass: %f kg', self.mass)
85 glog.debug('Stall force: %f N',
Tyler Chatow6738c362019-02-16 14:12:30 -080086 self.motor.stall_torque / self.G / self.radius)
Austin Schuh2e282d12024-02-19 12:00:58 -080087 glog.debug('Stall acceleration: %f m/s^2',
Tyler Chatow6738c362019-02-16 14:12:30 -080088 self.motor.stall_torque / self.G / self.radius / self.mass)
Austin Schuhb5d302f2019-01-20 20:51:19 -080089
Austin Schuh2e282d12024-02-19 12:00:58 -080090 holding_force = 500
91
92 holding_current = holding_force * self.radius * self.G / self.motor.Kt
93 glog.debug('Motor current to hold %f N: %f A', holding_force,
94 holding_current)
95 glog.debug(
96 'Battery current to hold %f N: %f A', holding_force,
97 holding_current * holding_current * self.motor.resistance / 12.0)
98
99 glog.debug('Free speed is %f m/s',
Austin Schuhb5d302f2019-01-20 20:51:19 -0800100 -self.B_continuous[1, 0] / self.A_continuous[1, 1] * 12.0)
101
102 self.Q = numpy.matrix([[(1.0 / (self.params.q_pos**2.0)), 0.0],
103 [0.0, (1.0 / (self.params.q_vel**2.0))]])
104
105 self.R = numpy.matrix([[(1.0 / (12.0**2.0))]])
106 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
107
108 q_pos_ff = 0.005
109 q_vel_ff = 1.0
110 self.Qff = numpy.matrix([[(1.0 / (q_pos_ff**2.0)), 0.0],
111 [0.0, (1.0 / (q_vel_ff**2.0))]])
112
113 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
114
115 glog.debug('K %s', repr(self.K))
116 glog.debug('Poles are %s',
117 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
118
119 self.Q = numpy.matrix([[(self.params.kalman_q_pos**2.0), 0.0],
120 [0.0, (self.params.kalman_q_vel**2.0)]])
121
122 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
123
Ravago Jones5127ccc2022-07-31 16:32:45 -0700124 self.KalmanGain, self.Q_steady = controls.kalman(A=self.A,
125 B=self.B,
126 C=self.C,
127 Q=self.Q,
128 R=self.R)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800129
130 glog.debug('Kal %s', repr(self.KalmanGain))
131
132 # The box formed by U_min and U_max must encompass all possible values,
133 # or else Austin's code gets angry.
134 self.U_max = numpy.matrix([[12.0]])
135 self.U_min = numpy.matrix([[-12.0]])
136
137 self.InitializeState()
138
139
140class IntegralLinearSystem(LinearSystem):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700141
Austin Schuhb5d302f2019-01-20 20:51:19 -0800142 def __init__(self, params, name='IntegralLinearSystem'):
143 super(IntegralLinearSystem, self).__init__(params, name=name)
144
Austin Schuhb5d302f2019-01-20 20:51:19 -0800145 self.A_continuous_unaugmented = self.A_continuous
146 self.B_continuous_unaugmented = self.B_continuous
147
148 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
149 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
150 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
151
152 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
153 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
154
155 self.C_unaugmented = self.C
156 self.C = numpy.matrix(numpy.zeros((1, 3)))
157 self.C[0:1, 0:2] = self.C_unaugmented
158
159 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
160 self.B_continuous, self.dt)
161
Tyler Chatow6738c362019-02-16 14:12:30 -0800162 self.Q = numpy.matrix([[(self.params.kalman_q_pos**2.0), 0.0, 0.0],
163 [0.0, (self.params.kalman_q_vel**2.0), 0.0],
Ravago Jones5127ccc2022-07-31 16:32:45 -0700164 [0.0, 0.0,
165 (self.params.kalman_q_voltage**2.0)]])
Austin Schuhb5d302f2019-01-20 20:51:19 -0800166
167 self.R = numpy.matrix([[(self.params.kalman_r_position**2.0)]])
168
Ravago Jones5127ccc2022-07-31 16:32:45 -0700169 self.KalmanGain, self.Q_steady = controls.kalman(A=self.A,
170 B=self.B,
171 C=self.C,
172 Q=self.Q,
173 R=self.R)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800174
175 self.K_unaugmented = self.K
176 self.K = numpy.matrix(numpy.zeros((1, 3)))
177 self.K[0, 0:2] = self.K_unaugmented
178 self.K[0, 2] = 1
179
180 self.Kff = numpy.concatenate(
181 (self.Kff, numpy.matrix(numpy.zeros((1, 1)))), axis=1)
182
183 self.InitializeState()
184
185
186def RunTest(plant,
187 end_goal,
188 controller,
189 observer=None,
190 duration=1.0,
191 use_profile=True,
192 kick_time=0.5,
193 kick_magnitude=0.0,
Lee Mracek28795ef2019-01-27 05:29:37 -0500194 max_velocity=0.3,
195 max_acceleration=10.0):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800196 """Runs the plant with an initial condition and goal.
197
198 Args:
199 plant: plant object to use.
200 end_goal: end_goal state.
Austin Schuh2e554032019-01-21 15:07:27 -0800201 controller: LinearSystem object to get K from, or None if we should
Austin Schuhb5d302f2019-01-20 20:51:19 -0800202 use plant.
Austin Schuh2e554032019-01-21 15:07:27 -0800203 observer: LinearSystem object to use for the observer, or None if we
204 should use the actual state.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800205 duration: float, time in seconds to run the simulation for.
206 kick_time: float, time in seconds to kick the robot.
207 kick_magnitude: float, disturbance in volts to apply.
208 max_velocity: float, the max speed in m/s to profile.
Lee Mracek28795ef2019-01-27 05:29:37 -0500209 max_acceleration: float, the max acceleration in m/s/s to profile.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800210 """
211 t_plot = []
212 x_plot = []
213 v_plot = []
214 a_plot = []
Austin Schuh2e282d12024-02-19 12:00:58 -0800215 motor_current_plot = []
216 battery_current_plot = []
Austin Schuhb5d302f2019-01-20 20:51:19 -0800217 x_goal_plot = []
218 v_goal_plot = []
219 x_hat_plot = []
220 u_plot = []
Austin Schuh2e282d12024-02-19 12:00:58 -0800221 power_rotor_plot = []
222 power_mechanism_plot = []
223 power_overall_plot = []
224 power_electrical_plot = []
Austin Schuhb5d302f2019-01-20 20:51:19 -0800225 offset_plot = []
226
227 if controller is None:
228 controller = plant
229
230 vbat = 12.0
231
Tyler Chatow6738c362019-02-16 14:12:30 -0800232 goal = numpy.concatenate((plant.X, numpy.matrix(numpy.zeros((1, 1)))),
233 axis=0)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800234
235 profile = TrapezoidProfile(plant.dt)
Lee Mracek28795ef2019-01-27 05:29:37 -0500236 profile.set_maximum_acceleration(max_acceleration)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800237 profile.set_maximum_velocity(max_velocity)
238 profile.SetGoal(goal[0, 0])
239
240 U_last = numpy.matrix(numpy.zeros((1, 1)))
241 iterations = int(duration / plant.dt)
Austin Schuh5ea48472021-02-02 20:46:41 -0800242 for i in range(iterations):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800243 t = i * plant.dt
244 observer.Y = plant.Y
245 observer.CorrectObserver(U_last)
246
247 offset_plot.append(observer.X_hat[2, 0])
248 x_hat_plot.append(observer.X_hat[0, 0])
249
Ravago Jones5127ccc2022-07-31 16:32:45 -0700250 next_goal = numpy.concatenate((profile.Update(
251 end_goal[0, 0], end_goal[1, 0]), numpy.matrix(numpy.zeros(
252 (1, 1)))),
253 axis=0)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800254
255 ff_U = controller.Kff * (next_goal - observer.A * goal)
256
257 if use_profile:
258 U_uncapped = controller.K * (goal - observer.X_hat) + ff_U
259 x_goal_plot.append(goal[0, 0])
260 v_goal_plot.append(goal[1, 0])
261 else:
262 U_uncapped = controller.K * (end_goal - observer.X_hat)
263 x_goal_plot.append(end_goal[0, 0])
264 v_goal_plot.append(end_goal[1, 0])
265
266 U = U_uncapped.copy()
267 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
Austin Schuh2e282d12024-02-19 12:00:58 -0800268
269 motor_current = (U[0, 0] - plant.X[1, 0] / plant.radius / plant.G /
270 plant.motor.Kv) / plant.motor.resistance
271 motor_current_plot.append(motor_current)
272 battery_current = U[0, 0] * motor_current / vbat
273 power_electrical_plot.append(battery_current * vbat)
274 battery_current_plot.append(battery_current)
275
276 # Instantaneous acceleration.
277 X_dot = plant.A_continuous * plant.X + plant.B_continuous * U
278 # Torque = J * alpha (accel).
279 power_rotor_plot.append(X_dot[1, 0] * plant.mass_motor * plant.X[1, 0])
280 power_mechanism_plot.append(X_dot[1, 0] * plant.params.mass *
281 plant.X[1, 0])
282 power_overall_plot.append(X_dot[1, 0] * plant.mass * plant.X[1, 0])
283
Austin Schuhb5d302f2019-01-20 20:51:19 -0800284 x_plot.append(plant.X[0, 0])
285
286 if v_plot:
287 last_v = v_plot[-1]
288 else:
289 last_v = 0
290
291 v_plot.append(plant.X[1, 0])
292 a_plot.append((v_plot[-1] - last_v) / plant.dt)
293
294 u_offset = 0.0
295 if t >= kick_time:
296 u_offset = kick_magnitude
297 plant.Update(U + u_offset)
298
299 observer.PredictObserver(U)
300
301 t_plot.append(t)
302 u_plot.append(U[0, 0])
303
304 ff_U -= U_uncapped - U
305 goal = controller.A * goal + controller.B * ff_U
306
307 if U[0, 0] != U_uncapped[0, 0]:
Ravago Jones5127ccc2022-07-31 16:32:45 -0700308 profile.MoveCurrentState(numpy.matrix([[goal[0, 0]], [goal[1,
309 0]]]))
Austin Schuhb5d302f2019-01-20 20:51:19 -0800310
311 glog.debug('Time: %f', t_plot[-1])
312 glog.debug('goal_error %s', repr(end_goal - goal))
313 glog.debug('error %s', repr(observer.X_hat - end_goal))
314
Austin Schuh2e282d12024-02-19 12:00:58 -0800315 pylab.suptitle(f'Gear ratio {plant.G}')
316 position_ax1 = pylab.subplot(3, 1, 1)
317 position_ax1.plot(t_plot, x_plot, label='x')
318 position_ax1.plot(t_plot, x_hat_plot, label='x_hat')
319 position_ax1.plot(t_plot, x_goal_plot, label='x_goal')
Austin Schuhb5d302f2019-01-20 20:51:19 -0800320
Austin Schuh2e282d12024-02-19 12:00:58 -0800321 power_ax2 = position_ax1.twinx()
322 power_ax2.set_xlabel("time(s)")
323 power_ax2.set_ylabel("Power (W)")
324 power_ax2.plot(t_plot, power_rotor_plot, label='Rotor power')
325 power_ax2.plot(t_plot, power_mechanism_plot, label='Mechanism power')
326 power_ax2.plot(t_plot,
327 power_overall_plot,
328 label='Overall mechanical power')
329 power_ax2.plot(t_plot, power_electrical_plot, label='Electrical power')
Austin Schuhb5d302f2019-01-20 20:51:19 -0800330
Austin Schuh2e282d12024-02-19 12:00:58 -0800331 position_ax1.legend()
332 power_ax2.legend(loc='lower right')
333
334 voltage_ax1 = pylab.subplot(3, 1, 2)
335 voltage_ax1.plot(t_plot, u_plot, label='u')
336 voltage_ax1.plot(t_plot, offset_plot, label='voltage_offset')
337 voltage_ax1.legend()
338
339 ax1 = pylab.subplot(3, 1, 3)
340 ax1.set_xlabel("time(s)")
341 ax1.set_ylabel("m/s^2")
342 ax1.plot(t_plot, a_plot, label='acceleration')
343
344 ax2 = ax1.twinx()
345 ax2.set_xlabel("time(s)")
346 ax2.set_ylabel("Amps")
347 ax2.plot(t_plot, battery_current_plot, 'g', label='battery')
348 ax2.plot(t_plot, motor_current_plot, 'r', label='motor')
349 ax2.legend()
350 ax1.legend(loc='lower left')
Austin Schuhb5d302f2019-01-20 20:51:19 -0800351
352 pylab.show()
353
354
Austin Schuh9d9d3742019-02-15 23:00:13 -0800355def PlotStep(params, R, plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800356 """Plots a step move to the goal.
357
358 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800359 params: LinearSystemParams for the controller and observer
360 plant_params: LinearSystemParams for the plant. Defaults to params if
361 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800362 R: numpy.matrix(2, 1), the goal"""
Austin Schuh9d9d3742019-02-15 23:00:13 -0800363 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800364 controller = IntegralLinearSystem(params, params.name)
365 observer = IntegralLinearSystem(params, params.name)
366
367 # Test moving the system.
368 initial_X = numpy.matrix([[0.0], [0.0]])
369 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
370 augmented_R[0:2, :] = R
Ravago Jones5127ccc2022-07-31 16:32:45 -0700371 RunTest(plant,
372 end_goal=augmented_R,
373 controller=controller,
374 observer=observer,
375 duration=2.0,
376 use_profile=False,
377 kick_time=1.0,
378 kick_magnitude=0.0)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800379
380
Austin Schuh9d9d3742019-02-15 23:00:13 -0800381def PlotKick(params, R, plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800382 """Plots a step motion with a kick at 1.0 seconds.
383
384 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800385 params: LinearSystemParams for the controller and observer
386 plant_params: LinearSystemParams for the plant. Defaults to params if
387 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800388 R: numpy.matrix(2, 1), the goal"""
Austin Schuh9d9d3742019-02-15 23:00:13 -0800389 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800390 controller = IntegralLinearSystem(params, params.name)
391 observer = IntegralLinearSystem(params, params.name)
392
393 # Test moving the system.
394 initial_X = numpy.matrix([[0.0], [0.0]])
395 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
396 augmented_R[0:2, :] = R
Ravago Jones5127ccc2022-07-31 16:32:45 -0700397 RunTest(plant,
398 end_goal=augmented_R,
399 controller=controller,
400 observer=observer,
401 duration=2.0,
402 use_profile=False,
403 kick_time=1.0,
404 kick_magnitude=2.0)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800405
406
Austin Schuh9d9d3742019-02-15 23:00:13 -0800407def PlotMotion(params,
408 R,
409 max_velocity=0.3,
410 max_acceleration=10.0,
411 plant_params=None):
Austin Schuhb5d302f2019-01-20 20:51:19 -0800412 """Plots a trapezoidal motion.
413
414 Args:
Austin Schuh9d9d3742019-02-15 23:00:13 -0800415 params: LinearSystemParams for the controller and observer
416 plant_params: LinearSystemParams for the plant. Defaults to params if
417 plant_params is None.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800418 R: numpy.matrix(2, 1), the goal,
419 max_velocity: float, The max velocity of the profile.
Lee Mracek28795ef2019-01-27 05:29:37 -0500420 max_acceleration: float, The max acceleration of the profile.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800421 """
Austin Schuh9d9d3742019-02-15 23:00:13 -0800422 plant = LinearSystem(plant_params or params, params.name)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800423 controller = IntegralLinearSystem(params, params.name)
424 observer = IntegralLinearSystem(params, params.name)
425
426 # Test moving the system.
427 initial_X = numpy.matrix([[0.0], [0.0]])
428 augmented_R = numpy.matrix(numpy.zeros((3, 1)))
429 augmented_R[0:2, :] = R
Ravago Jones5127ccc2022-07-31 16:32:45 -0700430 RunTest(plant,
431 end_goal=augmented_R,
432 controller=controller,
433 observer=observer,
Austin Schuh2e282d12024-02-19 12:00:58 -0800434 duration=1.0,
Ravago Jones5127ccc2022-07-31 16:32:45 -0700435 use_profile=True,
436 max_velocity=max_velocity,
437 max_acceleration=max_acceleration)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800438
439
440def WriteLinearSystem(params, plant_files, controller_files, year_namespaces):
441 """Writes out the constants for a linear system to a file.
442
443 Args:
Tyler Chatowd3afdef2019-04-06 22:15:26 -0700444 params: list of LinearSystemParams or LinearSystemParams, the
445 parameters defining the system.
Austin Schuhb5d302f2019-01-20 20:51:19 -0800446 plant_files: list of strings, the cc and h files for the plant.
447 controller_files: list of strings, the cc and h files for the integral
448 controller.
449 year_namespaces: list of strings, the namespace list to use.
450 """
451 # Write the generated constants out to a file.
Tyler Chatowd3afdef2019-04-06 22:15:26 -0700452 linear_systems = []
453 integral_linear_systems = []
454
455 if type(params) is list:
456 name = params[0].name
457 for index, param in enumerate(params):
Ravago Jones26f7ad02021-02-05 15:45:59 -0800458 linear_systems.append(LinearSystem(param, param.name + str(index)))
Tyler Chatowd3afdef2019-04-06 22:15:26 -0700459 integral_linear_systems.append(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800460 IntegralLinearSystem(param,
461 'Integral' + param.name + str(index)))
Tyler Chatowd3afdef2019-04-06 22:15:26 -0700462 else:
463 name = params.name
464 linear_systems.append(LinearSystem(params, params.name))
465 integral_linear_systems.append(
466 IntegralLinearSystem(params, 'Integral' + params.name))
467
Ravago Jones5127ccc2022-07-31 16:32:45 -0700468 loop_writer = control_loop.ControlLoopWriter(name,
469 linear_systems,
470 namespaces=year_namespaces)
Austin Schuhb5d302f2019-01-20 20:51:19 -0800471 loop_writer.AddConstant(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800472 control_loop.Constant('kFreeSpeed', '%f',
473 linear_systems[0].motor.free_speed))
Austin Schuhb5d302f2019-01-20 20:51:19 -0800474 loop_writer.AddConstant(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800475 control_loop.Constant('kOutputRatio', '%f',
476 linear_systems[0].G * linear_systems[0].radius))
Alex Perry5fb5ff22019-02-09 21:53:17 -0800477 loop_writer.AddConstant(
Tyler Chatowd3afdef2019-04-06 22:15:26 -0700478 control_loop.Constant('kRadius', '%f', linear_systems[0].radius))
James Kuszmauleeb98e92024-01-14 22:15:32 -0800479 loop_writer.Write(plant_files[0], plant_files[1],
480 None if len(plant_files) < 3 else plant_files[2])
Austin Schuhb5d302f2019-01-20 20:51:19 -0800481
Austin Schuhb5d302f2019-01-20 20:51:19 -0800482 integral_loop_writer = control_loop.ControlLoopWriter(
Ravago Jones26f7ad02021-02-05 15:45:59 -0800483 'Integral' + name, integral_linear_systems, namespaces=year_namespaces)
James Kuszmauleeb98e92024-01-14 22:15:32 -0800484 integral_loop_writer.Write(
485 controller_files[0], controller_files[1],
486 None if len(controller_files) < 3 else controller_files[2])