Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | from frc971.control_loops.python import control_loop |
| 4 | from frc971.control_loops.python import controls |
| 5 | import numpy |
| 6 | import math |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 7 | import scipy.optimize |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 8 | import sys |
| 9 | import math |
| 10 | from y2022.control_loops.python import catapult_lib |
| 11 | from matplotlib import pylab |
| 12 | |
| 13 | import gflags |
| 14 | import glog |
| 15 | |
| 16 | FLAGS = gflags.FLAGS |
| 17 | |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 18 | gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.') |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 19 | |
| 20 | ball_mass = 0.25 |
| 21 | ball_diameter = 9.5 * 0.0254 |
| 22 | lever = 17.5 * 0.0254 |
| 23 | |
| 24 | G = (14.0 / 72.0) * (12.0 / 33.0) |
| 25 | |
| 26 | |
| 27 | def AddResistance(motor, resistance): |
| 28 | motor.resistance += resistance |
| 29 | return motor |
| 30 | |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 31 | |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 32 | J_ball = 1.5 * ball_mass * lever * lever |
| 33 | # Assuming carbon fiber, calculate the mass of the bar. |
| 34 | M_bar = (1750 * lever * 0.0254 * 0.0254 * (1.0 - (1 - 0.07)**2.0)) |
| 35 | # And the moment of inertia. |
| 36 | J_bar = 1.0 / 3.0 * M_bar * lever**2.0 |
| 37 | |
| 38 | # Do the same for a theoretical cup. Assume a 40 thou thick carbon cup. |
| 39 | M_cup = (1750 * 0.0254 * 0.04 * 2 * math.pi * (ball_diameter / 2.)**2.0) |
| 40 | J_cup = M_cup * lever**2.0 + M_cup * (ball_diameter / 2.)**2.0 |
| 41 | |
| 42 | print("J ball", ball_mass * lever * lever) |
| 43 | print("J bar", J_bar) |
| 44 | print("bar mass", M_bar) |
| 45 | print("J cup", J_cup) |
| 46 | print("cup mass", M_cup) |
| 47 | |
| 48 | J = (J_ball + J_bar + J_cup * 1.5) |
| 49 | print("J", J) |
| 50 | |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 51 | kCatapult = catapult_lib.CatapultParams( |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 52 | name='Finisher', |
| 53 | motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.03), |
| 54 | G=G, |
| 55 | J=J, |
| 56 | lever=lever, |
| 57 | q_pos=0.01, |
| 58 | q_vel=10.0, |
| 59 | q_voltage=4.0, |
| 60 | r_pos=0.01, |
| 61 | controller_poles=[.93], |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 62 | dt=0.00505) |
| 63 | |
| 64 | catapult = catapult_lib.Catapult(kCatapult) |
| 65 | |
| 66 | # Ideas for adjusting the cost function: |
| 67 | # |
| 68 | # Penalize battery current? |
| 69 | # Penalize accel/rotor current? |
| 70 | # Penalize velocity error off destination? |
| 71 | # Penalize max u |
| 72 | # |
| 73 | # Ramp up U cost over time? |
| 74 | # Once moving, open up saturation bounds |
| 75 | # |
| 76 | # We really want our cost function to be robust so that we can tolerate the |
| 77 | # battery not delivering like we want at the end. |
| 78 | |
| 79 | |
| 80 | def mpc_cost(X_initial, X_final, u_matrix): |
| 81 | X = X_initial.copy() |
| 82 | cost = 0.0 |
| 83 | last_u = u_matrix[0] |
| 84 | max_u = 0.0 |
| 85 | for count, u in enumerate(u_matrix): |
| 86 | v_prior = X[1, 0] |
| 87 | X = catapult.A * X + catapult.B * numpy.matrix([[u]]) |
| 88 | v = X[1, 0] |
| 89 | |
| 90 | # Smoothness cost on voltage change and voltage. |
| 91 | #cost += (u - last_u) ** 2.0 |
| 92 | #cost += (u - 6.0) ** 2.0 |
| 93 | |
| 94 | measured_a = (v - v_prior) / catapult.dt |
| 95 | expected_a = 0.0 |
| 96 | |
| 97 | # Our good cost! |
| 98 | cost_scalar = 0.02 * max(0.0, (20 - (len(u_matrix) - count)) / 20.) |
| 99 | cost += ((measured_a - expected_a) * cost_scalar)**2.0 |
| 100 | cost += (measured_a * 0.010)**2.0 |
| 101 | |
| 102 | # Quadratic cost. This delays as long as possible, but approximates a |
| 103 | # LQR until saturation. |
| 104 | #cost += (u - 0.0) ** 2.0 |
| 105 | #cost += (0.1 * (X_final[0, 0] - X[0, 0])) ** 2.0 |
| 106 | #cost += (0.5 * (X_final[1, 0] - X[1, 0])) ** 2.0 |
| 107 | |
| 108 | max_u = max(u, max_u) |
| 109 | last_u = u |
| 110 | |
| 111 | # Penalize max power usage. This is hard to solve. |
| 112 | #cost += max_u * 10 |
| 113 | |
| 114 | terminal_cost = (X - X_final).transpose() * numpy.matrix( |
| 115 | [[10000.0, 0.0], [0.0, 10000.0]]) * (X - X_final) |
| 116 | cost += terminal_cost[0, 0] |
| 117 | |
| 118 | return cost |
| 119 | |
| 120 | |
| 121 | def SolveCatapult(X_initial, X_final, u): |
| 122 | """ Solves for the optimal action given a seed, state, and target """ |
| 123 | def vbat_constraint(z, i): |
| 124 | return 12.0 - z[i] |
| 125 | |
| 126 | def forward(z, i): |
| 127 | return z[i] |
| 128 | |
| 129 | def mpc_cost2(u_matrix): |
| 130 | return mpc_cost(X_initial, X_final, u_matrix) |
| 131 | |
| 132 | constraints = [{ |
| 133 | 'type': 'ineq', |
| 134 | 'fun': vbat_constraint, |
| 135 | 'args': (i, ) |
| 136 | } for i in numpy.arange(len(u))] |
| 137 | |
| 138 | constraints += [{ |
| 139 | 'type': 'ineq', |
| 140 | 'fun': forward, |
| 141 | 'args': (i, ) |
| 142 | } for i in numpy.arange(len(u))] |
| 143 | |
| 144 | result = scipy.optimize.minimize(mpc_cost2, |
| 145 | u, |
| 146 | method='SLSQP', |
| 147 | constraints=constraints) |
| 148 | print(result) |
| 149 | |
| 150 | return result.x |
| 151 | |
| 152 | |
| 153 | def CatapultProblem(): |
| 154 | c = catapult_lib.Catapult(kCatapult) |
| 155 | |
| 156 | kHorizon = 40 |
| 157 | |
| 158 | u = [0.0] * kHorizon |
| 159 | X_initial = numpy.matrix([[0.0], [0.0]]) |
| 160 | X_final = numpy.matrix([[2.0], [25.0]]) |
| 161 | |
| 162 | |
| 163 | X_initial = numpy.matrix([[0.0], [0.0]]) |
| 164 | X = X_initial.copy() |
| 165 | |
| 166 | t_samples = [0.0] |
| 167 | x_samples = [0.0] |
| 168 | v_samples = [0.0] |
| 169 | a_samples = [0.0] |
| 170 | |
| 171 | # Iteratively solve our MPC and simulate it. |
| 172 | u_samples = [] |
| 173 | for i in range(kHorizon): |
| 174 | u_horizon = SolveCatapult(X, X_final, u) |
| 175 | u_samples.append(u_horizon[0]) |
| 176 | v_prior = X[1, 0] |
| 177 | X = c.A * X + c.B * numpy.matrix([[u_horizon[0]]]) |
| 178 | v = X[1, 0] |
| 179 | t_samples.append(t_samples[-1] + c.dt) |
| 180 | x_samples.append(X[0, 0]) |
| 181 | v_samples.append(X[1, 0]) |
| 182 | a_samples.append((v - v_prior) / c.dt) |
| 183 | |
| 184 | u = u_horizon[1:] |
| 185 | |
| 186 | print('Final state', X.transpose()) |
| 187 | print('Final velocity', X[1, 0] * lever) |
| 188 | pylab.subplot(2, 1, 1) |
| 189 | pylab.plot(t_samples, x_samples, label="x") |
| 190 | pylab.plot(t_samples, v_samples, label="v") |
| 191 | pylab.plot(t_samples[1:], u_samples, label="u") |
| 192 | pylab.legend() |
| 193 | pylab.subplot(2, 1, 2) |
| 194 | pylab.plot(t_samples, a_samples, label="a") |
| 195 | pylab.legend() |
| 196 | |
| 197 | pylab.show() |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 198 | |
| 199 | |
| 200 | def main(argv): |
| 201 | # Do all our math with a lower voltage so we have headroom. |
| 202 | U = numpy.matrix([[9.0]]) |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 203 | print( |
| 204 | "For G:", G, " max speed ", |
| 205 | catapult_lib.MaxSpeed(params=kCatapult, |
| 206 | U=U, |
| 207 | final_position=math.pi / 2.0)) |
| 208 | |
| 209 | CatapultProblem() |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 210 | |
| 211 | if FLAGS.plot: |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 212 | catapult_lib.PlotShot(kCatapult, U, final_position=math.pi / 4.0) |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 213 | |
| 214 | gs = [] |
| 215 | speed = [] |
| 216 | for i in numpy.linspace(0.01, 0.15, 150): |
Austin Schuh | c3e0428 | 2022-02-12 20:00:53 -0800 | [diff] [blame^] | 217 | kCatapult.G = i |
| 218 | gs.append(kCatapult.G) |
| 219 | speed.append( |
| 220 | catapult_lib.MaxSpeed(params=kCatapult, |
| 221 | U=U, |
| 222 | final_position=math.pi / 2.0)) |
| 223 | pylab.plot(gs, speed, label="max_speed") |
Austin Schuh | 8216245 | 2022-02-07 22:01:45 -0800 | [diff] [blame] | 224 | pylab.show() |
| 225 | return 0 |
| 226 | |
| 227 | |
| 228 | if __name__ == '__main__': |
| 229 | argv = FLAGS(sys.argv) |
| 230 | sys.exit(main(argv)) |