blob: 8879dff845e748877c4371493f416877e950f0e2 [file] [log] [blame]
Austin Schuhf173eb82018-01-20 23:32:30 -08001#!/usr/bin/python
Michael Schuh10dd1e02018-01-20 13:19:44 -08002
Austin Schuhf173eb82018-01-20 23:32:30 -08003from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
Michael Schuh10dd1e02018-01-20 13:19:44 -08005import numpy
Austin Schuhf173eb82018-01-20 23:32:30 -08006import sys
7import matplotlib
8from matplotlib import pylab
9import gflags
10import glog
Michael Schuh10dd1e02018-01-20 13:19:44 -080011
Austin Schuhf173eb82018-01-20 23:32:30 -080012FLAGS = gflags.FLAGS
Michael Schuh10dd1e02018-01-20 13:19:44 -080013
Austin Schuhf173eb82018-01-20 23:32:30 -080014try:
Austin Schuh17dd0892018-03-02 20:06:31 -080015 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
Austin Schuhf173eb82018-01-20 23:32:30 -080016except gflags.DuplicateFlagError:
Austin Schuh17dd0892018-03-02 20:06:31 -080017 pass
18
Michael Schuh10dd1e02018-01-20 13:19:44 -080019
Austin Schuhf173eb82018-01-20 23:32:30 -080020class Intake(control_loop.ControlLoop):
Austin Schuh17dd0892018-03-02 20:06:31 -080021 def __init__(self, name="Intake"):
22 super(Intake, self).__init__(name)
23 self.motor = control_loop.BAG()
24 # Stall Torque in N m
25 self.stall_torque = self.motor.stall_torque
26 # Stall Current in Amps
27 self.stall_current = self.motor.stall_current
28 # Free Speed in RPM
29 self.free_speed = self.motor.free_speed
30 # Free Current in Amps
31 self.free_current = self.motor.free_current
Michael Schuh10dd1e02018-01-20 13:19:44 -080032
Austin Schuh17dd0892018-03-02 20:06:31 -080033 # Resistance of the motor
34 self.resistance = self.motor.resistance
35 # Motor velocity constant
36 self.Kv = self.motor.Kv
37 # Torque constant
38 self.Kt = self.motor.Kt
39 # Gear ratio
40 self.G = 1.0 / 102.6
Michael Schuh10dd1e02018-01-20 13:19:44 -080041
Austin Schuh17dd0892018-03-02 20:06:31 -080042 self.motor_inertia = 0.00000589 * 1.2
Michael Schuh10dd1e02018-01-20 13:19:44 -080043
Austin Schuh17dd0892018-03-02 20:06:31 -080044 # Series elastic moment of inertia
45 self.Je = self.motor_inertia / (self.G * self.G)
46 # Grabber moment of inertia
47 self.Jo = 0.0363
Michael Schuh10dd1e02018-01-20 13:19:44 -080048
Austin Schuh17dd0892018-03-02 20:06:31 -080049 # Bot has a time constant of 0.22
50 # Current physics has a time constant of 0.18
Michael Schuh10dd1e02018-01-20 13:19:44 -080051
Austin Schuh17dd0892018-03-02 20:06:31 -080052 # Spring constant (N m / radian)
53 self.Ks = 32.74
Michael Schuh10dd1e02018-01-20 13:19:44 -080054
Austin Schuh17dd0892018-03-02 20:06:31 -080055 # Control loop time step
56 self.dt = 0.00505
Michael Schuh10dd1e02018-01-20 13:19:44 -080057
Austin Schuh17dd0892018-03-02 20:06:31 -080058 # State is [output_position, output_velocity,
59 # elastic_position, elastic_velocity]
60 # The output position is the absolute position of the intake arm.
61 # The elastic position is the absolute position of the motor side of the
62 # series elastic.
63 # Input is [voltage]
Michael Schuh10dd1e02018-01-20 13:19:44 -080064
Austin Schuh17dd0892018-03-02 20:06:31 -080065 self.A_continuous = numpy.matrix(
66 [[0.0, 1.0, 0.0, 0.0],
67 [(-self.Ks / self.Jo), 0.0, (self.Ks / self.Jo), 0.0],
68 [0.0, 0.0, 0.0, 1.0],
69 [(self.Ks / self.Je), 0.0, (-self.Ks / self.Je), \
70 -self.Kt / (self.Je * self.resistance * self.Kv * self.G * self.G)]])
Michael Schuh10dd1e02018-01-20 13:19:44 -080071
Austin Schuh17dd0892018-03-02 20:06:31 -080072 # Start with the unmodified input
73 self.B_continuous = numpy.matrix(
74 [[0.0], [0.0], [0.0],
75 [self.Kt / (self.G * self.Je * self.resistance)]])
Michael Schuh10dd1e02018-01-20 13:19:44 -080076
Austin Schuh17dd0892018-03-02 20:06:31 -080077 self.C = numpy.matrix([[1.0, 0.0, -1.0, 0.0], [0.0, 0.0, 1.0, 0.0]])
78 self.D = numpy.matrix([[0.0], [0.0]])
Michael Schuh10dd1e02018-01-20 13:19:44 -080079
Austin Schuh17dd0892018-03-02 20:06:31 -080080 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
81 self.B_continuous, self.dt)
Michael Schuh10dd1e02018-01-20 13:19:44 -080082
Austin Schuh17dd0892018-03-02 20:06:31 -080083 #controllability = controls.ctrb(self.A, self.B)
84 #glog.debug('ctrb: ' + repr(numpy.linalg.matrix_rank(controllability)))
Michael Schuh10dd1e02018-01-20 13:19:44 -080085
Austin Schuh17dd0892018-03-02 20:06:31 -080086 #observability = controls.ctrb(self.A.T, self.C.T)
87 #glog.debug('obs: ' + repr(numpy.linalg.matrix_rank(observability)))
Michael Schuh10dd1e02018-01-20 13:19:44 -080088
Austin Schuh17dd0892018-03-02 20:06:31 -080089 glog.debug('A_continuous ' + repr(self.A_continuous))
90 glog.debug('B_continuous ' + repr(self.B_continuous))
Michael Schuh10dd1e02018-01-20 13:19:44 -080091
Austin Schuh17dd0892018-03-02 20:06:31 -080092 self.K = numpy.matrix(numpy.zeros((1, 4)))
Austin Schuhf173eb82018-01-20 23:32:30 -080093
Austin Schuh17dd0892018-03-02 20:06:31 -080094 q_pos = 0.05
95 q_vel = 2.65
96 self.Q = numpy.matrix(
97 numpy.diag([(q_pos**2.0), (q_vel**2.0), (q_pos**2.0), (q_vel
98 **2.0)]))
Austin Schuhf173eb82018-01-20 23:32:30 -080099
Austin Schuh17dd0892018-03-02 20:06:31 -0800100 r_nm = 0.025
101 self.R = numpy.matrix(numpy.diag([(r_nm**2.0), (r_nm**2.0)]))
Austin Schuhf173eb82018-01-20 23:32:30 -0800102
Austin Schuh17dd0892018-03-02 20:06:31 -0800103 self.KalmanGain, self.Q_steady = controls.kalman(
104 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
Austin Schuhf173eb82018-01-20 23:32:30 -0800105
Austin Schuh17dd0892018-03-02 20:06:31 -0800106 # The box formed by U_min and U_max must encompass all possible values,
107 # or else Austin's code gets angry.
108 self.U_max = numpy.matrix([[12.0]])
109 self.U_min = numpy.matrix([[-12.0]])
110
111 self.InitializeState()
Austin Schuhf173eb82018-01-20 23:32:30 -0800112
113
114class DelayedIntake(Intake):
Austin Schuh17dd0892018-03-02 20:06:31 -0800115 def __init__(self, name="DelayedIntake"):
116 super(DelayedIntake, self).__init__(name=name)
Austin Schuhf173eb82018-01-20 23:32:30 -0800117
Austin Schuh17dd0892018-03-02 20:06:31 -0800118 self.A_undelayed = self.A
119 self.B_undelayed = self.B
Austin Schuhf173eb82018-01-20 23:32:30 -0800120
Austin Schuh17dd0892018-03-02 20:06:31 -0800121 self.C_unaugmented = self.C
122 self.C = numpy.matrix(numpy.zeros((2, 5)))
123 self.C[0:2, 0:4] = self.C_unaugmented
Austin Schuhf173eb82018-01-20 23:32:30 -0800124
Austin Schuh17dd0892018-03-02 20:06:31 -0800125 # Model this as X[4] is the last power. And then B applies to the last
126 # power. This lets us model the 1 cycle PWM delay accurately.
127 self.A = numpy.matrix(numpy.zeros((5, 5)))
128 self.A[0:4, 0:4] = self.A_undelayed
129 self.A[0:4, 4] = self.B_undelayed
130 self.B = numpy.matrix(numpy.zeros((5, 1)))
131 self.B[4, 0] = 1.0
Austin Schuhf173eb82018-01-20 23:32:30 -0800132
Austin Schuh17dd0892018-03-02 20:06:31 -0800133 # Coordinate transform fom absolute angles to relative angles.
134 # [output_position, output_velocity, spring_angle, spring_velocity, voltage]
135 abs_to_rel = numpy.matrix(
136 [[1.0, 0.0, 0.0, 0.0, 0.0],
137 [0.0, 1.0, 0.0, 0.0, 0.0],
138 [1.0, 0.0, -1.0, 0.0, 0.0],
139 [0.0, 1.0, 0.0, -1.0, 0.0],
140 [0.0, 0.0, 0.0, 0.0, 1.0]])
141 # and back again.
142 rel_to_abs = numpy.matrix(numpy.linalg.inv(abs_to_rel))
Austin Schuhf173eb82018-01-20 23:32:30 -0800143
Austin Schuh17dd0892018-03-02 20:06:31 -0800144 # Now, get A and B in the relative coordinate system.
145 self.A_transformed_full = numpy.matrix(numpy.zeros((5, 5)))
146 self.B_transformed_full = numpy.matrix(numpy.zeros((5, 1)))
147 (self.A_transformed_full[0:4, 0:4],
148 self.A_transformed_full[0:4, 4]) = self.ContinuousToDiscrete(
149 abs_to_rel[0:4, 0:4] * self.A_continuous * rel_to_abs[0:4, 0:4],
150 abs_to_rel[0:4, 0:4] * self.B_continuous, self.dt)
151 self.B_transformed_full[4, 0] = 1.0
Austin Schuhf173eb82018-01-20 23:32:30 -0800152
Austin Schuh17dd0892018-03-02 20:06:31 -0800153 # Pull out the components of the dynamics which don't include the spring
154 # output position so we can do partial state feedback on what we care about.
155 self.A_transformed = self.A_transformed_full[1:5, 1:5]
156 self.B_transformed = self.B_transformed_full[1:5, 0]
Austin Schuhf173eb82018-01-20 23:32:30 -0800157
Austin Schuh17dd0892018-03-02 20:06:31 -0800158 glog.debug('A_transformed_full ' + str(self.A_transformed_full))
159 glog.debug('B_transformed_full ' + str(self.B_transformed_full))
160 glog.debug('A_transformed ' + str(self.A_transformed))
161 glog.debug('B_transformed ' + str(self.B_transformed))
Austin Schuhf173eb82018-01-20 23:32:30 -0800162
Austin Schuh17dd0892018-03-02 20:06:31 -0800163 # Now, let's design a controller in
164 # [output_velocity, spring_position, spring_velocity, delayed_voltage]
165 # space.
Austin Schuhf173eb82018-01-20 23:32:30 -0800166
Austin Schuh17dd0892018-03-02 20:06:31 -0800167 q_output_vel = 1.0
168 q_spring_pos = 0.5
169 q_spring_vel = 2.0
170 q_voltage = 1000000000000.0
171 self.Q_lqr = numpy.matrix(
172 numpy.diag([
173 1.0 / (q_output_vel**2.0), 1.0 / (q_spring_pos**2.0),
174 1.0 / (q_spring_vel**2.0), 1.0 / (q_voltage**2.0)
175 ]))
Austin Schuhf173eb82018-01-20 23:32:30 -0800176
Austin Schuh17dd0892018-03-02 20:06:31 -0800177 self.R = numpy.matrix([[(1.0 / (12.0**2.0))]])
Austin Schuhf173eb82018-01-20 23:32:30 -0800178
Austin Schuh17dd0892018-03-02 20:06:31 -0800179 self.K_transformed = controls.dlqr(
180 self.A_transformed, self.B_transformed, self.Q_lqr, self.R)
Austin Schuhf173eb82018-01-20 23:32:30 -0800181
Austin Schuh17dd0892018-03-02 20:06:31 -0800182 # Write the controller back out in the absolute coordinate system.
183 self.K = numpy.hstack((numpy.matrix([[0.0]]),
184 self.K_transformed)) * abs_to_rel
Austin Schuhf173eb82018-01-20 23:32:30 -0800185
Austin Schuh17dd0892018-03-02 20:06:31 -0800186 controllability = controls.ctrb(self.A_transformed, self.B_transformed)
187 glog.debug('ctrb: ' + repr(numpy.linalg.matrix_rank(controllability)))
Austin Schuhf173eb82018-01-20 23:32:30 -0800188
Austin Schuh17dd0892018-03-02 20:06:31 -0800189 w, v = numpy.linalg.eig(
190 self.A_transformed - self.B_transformed * self.K_transformed)
191 glog.debug('Poles are %s, for %s', repr(w), self._name)
Austin Schuhf173eb82018-01-20 23:32:30 -0800192
Austin Schuh17dd0892018-03-02 20:06:31 -0800193 for i in range(len(w)):
194 glog.debug(' Pole %s -> %s', repr(w[i]), v[:, i])
Austin Schuhf173eb82018-01-20 23:32:30 -0800195
Austin Schuh17dd0892018-03-02 20:06:31 -0800196 glog.debug('K is %s', repr(self.K_transformed))
Austin Schuhf173eb82018-01-20 23:32:30 -0800197
Austin Schuh17dd0892018-03-02 20:06:31 -0800198 # Design a kalman filter here as well.
199 q_pos = 0.05
200 q_vel = 2.65
201 q_volts = 0.005
202 self.Q = numpy.matrix(
203 numpy.diag([(q_pos**2.0), (q_vel**2.0), (q_pos**2.0), (q_vel**2.0),
204 (q_volts**2.0)]))
Austin Schuhf173eb82018-01-20 23:32:30 -0800205
Austin Schuh17dd0892018-03-02 20:06:31 -0800206 r_nm = 0.025
207 self.R = numpy.matrix(numpy.diag([(r_nm**2.0), (r_nm**2.0)]))
208
209 glog.debug('Overall poles are %s, for %s',
210 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]),
211 self._name)
212
213 self.KalmanGain, self.Q_steady = controls.kalman(
214 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
215
216 self.InitializeState()
Austin Schuhf173eb82018-01-20 23:32:30 -0800217
218
219class ScenarioPlotter(object):
Austin Schuh17dd0892018-03-02 20:06:31 -0800220 def __init__(self):
221 # Various lists for graphing things.
222 self.t = []
223 self.x_motor = []
224 self.x_output = []
225 self.v = []
226 self.goal_v = []
227 self.a = []
228 self.spring = []
229 self.x_hat = []
230 self.u = []
Austin Schuhf173eb82018-01-20 23:32:30 -0800231
Austin Schuh17dd0892018-03-02 20:06:31 -0800232 def run_test(self,
233 intake,
234 iterations=400,
235 controller_intake=None,
236 observer_intake=None):
237 """Runs the intake plant with an initial condition and goal.
Austin Schuhf173eb82018-01-20 23:32:30 -0800238
239 Test for whether the goal has been reached and whether the separation
240 goes outside of the initial and goal values by more than
241 max_separation_error.
242
243 Prints out something for a failure of either condition and returns
244 False if tests fail.
245 Args:
246 intake: intake object to use.
247 iterations: Number of timesteps to run the model for.
248 controller_intake: Intake object to get K from, or None if we should
249 use intake.
250 observer_intake: Intake object to use for the observer, or None if we
251 should use the actual state.
252 """
253
Austin Schuh17dd0892018-03-02 20:06:31 -0800254 if controller_intake is None:
255 controller_intake = intake
Austin Schuhf173eb82018-01-20 23:32:30 -0800256
Austin Schuh17dd0892018-03-02 20:06:31 -0800257 vbat = 12.0
Austin Schuhf173eb82018-01-20 23:32:30 -0800258
Austin Schuh17dd0892018-03-02 20:06:31 -0800259 if self.t:
260 initial_t = self.t[-1] + intake.dt
261 else:
262 initial_t = 0
Austin Schuhf173eb82018-01-20 23:32:30 -0800263
Austin Schuh17dd0892018-03-02 20:06:31 -0800264 # Delay U by 1 cycle in our simulation to make it more realistic.
265 last_U = numpy.matrix([[0.0]])
266 intake.Y = intake.C * intake.X
Austin Schuhf173eb82018-01-20 23:32:30 -0800267
Austin Schuh17dd0892018-03-02 20:06:31 -0800268 for i in xrange(iterations):
269 X_hat = intake.X
Austin Schuhf173eb82018-01-20 23:32:30 -0800270
Austin Schuh17dd0892018-03-02 20:06:31 -0800271 if observer_intake is not None:
272 X_hat = observer_intake.X_hat
273 self.x_hat.append(observer_intake.X_hat[0, 0])
Austin Schuhf173eb82018-01-20 23:32:30 -0800274
Austin Schuh17dd0892018-03-02 20:06:31 -0800275 goal_angle = 3.0
276 goal_velocity = numpy.clip((goal_angle - X_hat[0, 0]) * 6.0, -1.0,
277 1.0)
Austin Schuhf173eb82018-01-20 23:32:30 -0800278
Austin Schuh17dd0892018-03-02 20:06:31 -0800279 self.goal_v.append(goal_velocity)
Austin Schuhf173eb82018-01-20 23:32:30 -0800280
Austin Schuh17dd0892018-03-02 20:06:31 -0800281 # Nominal: 1.8 N at 0.25 m -> 0.45 N m
282 # Nominal: 13 N at 0.25 m at 0.5 radians -> 3.25 N m -> 6 N m / radian
Austin Schuhf173eb82018-01-20 23:32:30 -0800283
Austin Schuh17dd0892018-03-02 20:06:31 -0800284 R = numpy.matrix([[0.0], [goal_velocity], [0.0], [goal_velocity],
285 [goal_velocity / (intake.G * intake.Kv)]])
286 U = controller_intake.K * (R - X_hat) + R[4, 0]
Austin Schuhf173eb82018-01-20 23:32:30 -0800287
Austin Schuh17dd0892018-03-02 20:06:31 -0800288 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat) # * 0.0
Austin Schuhf173eb82018-01-20 23:32:30 -0800289
Austin Schuh17dd0892018-03-02 20:06:31 -0800290 self.x_output.append(intake.X[0, 0])
291 self.x_motor.append(intake.X[2, 0])
292 self.spring.append(intake.X[0, 0] - intake.X[2, 0])
Austin Schuhf173eb82018-01-20 23:32:30 -0800293
Austin Schuh17dd0892018-03-02 20:06:31 -0800294 if self.v:
295 last_v = self.v[-1]
296 else:
297 last_v = 0
Austin Schuhf173eb82018-01-20 23:32:30 -0800298
Austin Schuh17dd0892018-03-02 20:06:31 -0800299 self.v.append(intake.X[1, 0])
300 self.a.append((self.v[-1] - last_v) / intake.dt)
Austin Schuhf173eb82018-01-20 23:32:30 -0800301
Austin Schuh17dd0892018-03-02 20:06:31 -0800302 if observer_intake is not None:
303 observer_intake.Y = intake.Y
304 observer_intake.CorrectObserver(U)
Austin Schuhf173eb82018-01-20 23:32:30 -0800305
Austin Schuh17dd0892018-03-02 20:06:31 -0800306 intake.Update(last_U + 0.0)
Austin Schuhf173eb82018-01-20 23:32:30 -0800307
Austin Schuh17dd0892018-03-02 20:06:31 -0800308 if observer_intake is not None:
309 observer_intake.PredictObserver(U)
Austin Schuhf173eb82018-01-20 23:32:30 -0800310
Austin Schuh17dd0892018-03-02 20:06:31 -0800311 self.t.append(initial_t + i * intake.dt)
312 self.u.append(U[0, 0])
313 last_U = U
Austin Schuhf173eb82018-01-20 23:32:30 -0800314
Austin Schuh17dd0892018-03-02 20:06:31 -0800315 def Plot(self):
316 pylab.subplot(3, 1, 1)
317 pylab.plot(self.t, self.x_output, label='x output')
318 pylab.plot(self.t, self.x_motor, label='x motor')
319 pylab.plot(self.t, self.x_hat, label='x_hat')
320 pylab.legend()
Austin Schuhf173eb82018-01-20 23:32:30 -0800321
Austin Schuh17dd0892018-03-02 20:06:31 -0800322 spring_ax1 = pylab.subplot(3, 1, 2)
323 spring_ax1.plot(self.t, self.u, 'k', label='u')
324 spring_ax2 = spring_ax1.twinx()
325 spring_ax2.plot(self.t, self.spring, label='spring_angle')
326 spring_ax1.legend(loc=2)
327 spring_ax2.legend()
Austin Schuhf173eb82018-01-20 23:32:30 -0800328
Austin Schuh17dd0892018-03-02 20:06:31 -0800329 accel_ax1 = pylab.subplot(3, 1, 3)
330 accel_ax1.plot(self.t, self.a, 'r', label='a')
Austin Schuhf173eb82018-01-20 23:32:30 -0800331
Austin Schuh17dd0892018-03-02 20:06:31 -0800332 accel_ax2 = accel_ax1.twinx()
333 accel_ax2.plot(self.t, self.v, label='v')
334 accel_ax2.plot(self.t, self.goal_v, label='goal_v')
335 accel_ax1.legend(loc=2)
336 accel_ax2.legend()
Austin Schuhf173eb82018-01-20 23:32:30 -0800337
Austin Schuh17dd0892018-03-02 20:06:31 -0800338 pylab.show()
Austin Schuhf173eb82018-01-20 23:32:30 -0800339
340
341def main(argv):
Austin Schuh17dd0892018-03-02 20:06:31 -0800342 scenario_plotter = ScenarioPlotter()
Austin Schuhf173eb82018-01-20 23:32:30 -0800343
Austin Schuh17dd0892018-03-02 20:06:31 -0800344 intake = Intake()
345 intake.X[0, 0] = 0.0
346 intake_controller = DelayedIntake()
347 observer_intake = DelayedIntake()
348 observer_intake.X_hat[0, 0] = intake.X[0, 0]
Austin Schuhf173eb82018-01-20 23:32:30 -0800349
Austin Schuh17dd0892018-03-02 20:06:31 -0800350 # Test moving the intake with constant separation.
351 scenario_plotter.run_test(
352 intake,
353 controller_intake=intake_controller,
354 observer_intake=observer_intake,
355 iterations=200)
Austin Schuhf173eb82018-01-20 23:32:30 -0800356
Austin Schuh17dd0892018-03-02 20:06:31 -0800357 if FLAGS.plot:
358 scenario_plotter.Plot()
Austin Schuhf173eb82018-01-20 23:32:30 -0800359
Austin Schuh17dd0892018-03-02 20:06:31 -0800360 # Write the generated constants out to a file.
361 if len(argv) != 5:
362 glog.fatal(
363 'Expected .h file name and .cc file name for intake and delayed_intake.'
364 )
365 else:
366 namespaces = ['y2018', 'control_loops', 'superstructure', 'intake']
367 intake = Intake('Intake')
368 loop_writer = control_loop.ControlLoopWriter(
369 'Intake', [intake], namespaces=namespaces)
370 loop_writer.AddConstant(
371 control_loop.Constant('kGearRatio', '%f', intake.G))
372 loop_writer.AddConstant(
373 control_loop.Constant('kMotorVelocityConstant', '%f', intake.Kv))
374 loop_writer.AddConstant(
375 control_loop.Constant('kFreeSpeed', '%f', intake.free_speed))
376 loop_writer.Write(argv[1], argv[2])
Austin Schuhf173eb82018-01-20 23:32:30 -0800377
Austin Schuh17dd0892018-03-02 20:06:31 -0800378 delayed_intake = DelayedIntake('DelayedIntake')
379 loop_writer = control_loop.ControlLoopWriter(
380 'DelayedIntake', [delayed_intake], namespaces=namespaces)
381 loop_writer.Write(argv[3], argv[4])
382
Sabina Davis3922dfa2018-02-10 23:10:05 -0800383
Michael Schuh10dd1e02018-01-20 13:19:44 -0800384if __name__ == '__main__':
Austin Schuh17dd0892018-03-02 20:06:31 -0800385 argv = FLAGS(sys.argv)
386 glog.init()
387 sys.exit(main(argv))