blob: ccd4b06017edebc771ac88e89976a81daa2f6f19 [file] [log] [blame]
Austin Schuh1d731362022-02-22 13:57:55 -08001#ifndef Y2022_CONTROL_LOOPS_SUPERSTRUCTURE_CATAPULT_CATAPULT_H_
2#define Y2022_CONTROL_LOOPS_SUPERSTRUCTURE_CATAPULT_CATAPULT_H_
3
4#include "Eigen/Dense"
5#include "frc971/control_loops/state_feedback_loop.h"
6#include "glog/logging.h"
7#include "osqp++.h"
Austin Schuh39f26f62022-02-24 21:34:46 -08008#include "y2022/constants.h"
9#include "y2022/control_loops/superstructure/superstructure_goal_generated.h"
10#include "y2022/control_loops/superstructure/superstructure_position_generated.h"
11#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
Austin Schuh1d731362022-02-22 13:57:55 -080012
13namespace y2022 {
14namespace control_loops {
15namespace superstructure {
16namespace catapult {
17
18// MPC problem for a specified horizon. This contains all the state for the
19// solver, setters to modify the current and target state, and a way to fetch
20// the solution.
21class MPCProblem {
22 public:
23 MPCProblem(size_t horizon,
24 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P,
25 Eigen::Matrix<double, Eigen::Dynamic, 1> accel_q,
26 Eigen::Matrix<double, 2, 2> Af,
27 Eigen::Matrix<double, Eigen::Dynamic, 2> final_q);
28
29 MPCProblem(MPCProblem const &) = delete;
30 void operator=(MPCProblem const &x) = delete;
31
32 // Sets the current and final state. This keeps the problem in tact and
33 // doesn't recreate it, so it will be fast.
34 void SetState(Eigen::Matrix<double, 2, 1> X_initial,
35 Eigen::Matrix<double, 2, 1> X_final);
36
37 // Solves our problem.
38 bool Solve();
39
40 double solve_time() const { return solve_time_; }
41
42 // Returns the solution that the solver found when Solve was last called.
43 double U(size_t i) const { return solver_.primal_solution()(i); }
44
45 // Returns the number of U's to be solved.
46 size_t horizon() const { return horizon_; }
47
48 // Warm starts the optimizer with the provided solution to make it solve
49 // faster.
50 void WarmStart(const MPCProblem &p);
51
52 private:
53 // The number of u's to solve for.
54 const size_t horizon_;
55
56 // The problem statement variables needed by SetState to update q.
57 const Eigen::Matrix<double, Eigen::Dynamic, 1> accel_q_;
58 const Eigen::Matrix<double, 2, 2> Af_;
59 const Eigen::Matrix<double, Eigen::Dynamic, 2> final_q_;
60
61 Eigen::Matrix<double, 2, 1> X_initial_;
62 Eigen::Matrix<double, 2, 1> X_final_;
63
Austin Schuh39f26f62022-02-24 21:34:46 -080064 Eigen::Matrix<double, Eigen::Dynamic, 1> objective_vector_;
65
Austin Schuh1d731362022-02-22 13:57:55 -080066 // Solver state.
67 osqp::OsqpInstance instance_;
68 osqp::OsqpSolver solver_;
69 osqp::OsqpSettings settings_;
70
71 double solve_time_ = 0;
72};
73
74// Decently efficient problem generator for multiple horizons given a max
75// horizon to solve for.
76//
77// The math is documented in mpc.tex
78class CatapultProblemGenerator {
79 public:
80 // Builds a problem generator for the specified max horizon and caches a lot
81 // of the state.
82 CatapultProblemGenerator(size_t horizon);
83
84 // Returns the maximum horizon.
85 size_t horizon() const { return horizon_; }
86
87 // Makes a problem for the specificed horizon.
88 std::unique_ptr<MPCProblem> MakeProblem(size_t horizon);
89
90 // Returns the P and Q matrices for the problem statement.
91 // cost = 0.5 X.T P X + q.T X
92 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P(size_t horizon);
93 const Eigen::Matrix<double, Eigen::Dynamic, 1> q(
94 size_t horizon, Eigen::Matrix<double, 2, 1> X_initial,
95 Eigen::Matrix<double, 2, 1> X_final);
96
97 private:
98 const Eigen::Matrix<double, Eigen::Dynamic, 1> accel_q(size_t horizon);
99
100 const Eigen::Matrix<double, 2, 2> Af(size_t horizon);
101 const Eigen::Matrix<double, 2, Eigen::Dynamic> Bf(size_t horizon);
102 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Pi(
103 size_t horizon);
104
105 // These functions are used in the constructor to build up the matrices below.
106 Eigen::Matrix<double, Eigen::Dynamic, 2> MakeAs();
107 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> MakeBs();
108 Eigen::Matrix<double, Eigen::Dynamic, 1> Makem();
109 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> MakeM();
110 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> MakeW();
111 Eigen::Matrix<double, Eigen::Dynamic, 1> Makew();
112 Eigen::DiagonalMatrix<double, Eigen::Dynamic> MakePi();
113 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> MakeP();
114
115 const StateFeedbackPlant<2, 1, 1> plant_;
116 const size_t horizon_;
117
118 const Eigen::DiagonalMatrix<double, 2> Q_final_;
119
120 const Eigen::Matrix<double, Eigen::Dynamic, 2> As_;
121 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Bs_;
122 const Eigen::Matrix<double, Eigen::Dynamic, 1> m_;
123 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> M_;
124
125 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> W_;
126 const Eigen::Matrix<double, Eigen::Dynamic, 1> w_;
127 const Eigen::DiagonalMatrix<double, Eigen::Dynamic> Pi_;
128
129 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> WM_;
130 const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Wmpw_;
131};
132
Austin Schuh39f26f62022-02-24 21:34:46 -0800133// A class to hold all the state needed to manage the catapult MPC solvers for
134// repeated shots.
135//
136// The solver may take a couple of cycles to get everything converged and ready.
137// The flow is as follows:
138// 1) Reset() the state for the new problem.
139// 2) Update to the current state with SetState()
140// 3) Call Solve(). This will return true if it is ready to be executed, false
141// if it needs more iterations to fully converge.
142// 4) Next() returns the current optimal control output and advances the
143// pointers to the next problem.
144// 5) Go back to 2 for the next cycle.
145class CatapultController {
146 public:
147 CatapultController(size_t horizon);
148
149 // Starts back over at the first controller.
150 void Reset();
151
152 // Updates our current and final states for the current controller.
153 void SetState(Eigen::Matrix<double, 2, 1> X_initial,
154 Eigen::Matrix<double, 2, 1> X_final);
155
156 // Solves! Returns true if the solution converged and osqp was happy.
157 bool Solve();
158
159 // Returns the time in seconds it last took Solve to run.
160 double solve_time() const { return solve_time_; }
161
162 // Returns the controller value if there is a controller to run, or nullopt if
163 // we finished the last controller. Advances the controller pointer to the
164 // next controller and warms up the next controller.
165 std::optional<double> Next();
166
167 // Returns true if Next has been called and a controller has been used. Reset
168 // starts over.
169 bool started() const { return current_controller_ != 0u; }
170
171 private:
172 CatapultProblemGenerator generator_;
173
174 std::vector<std::unique_ptr<MPCProblem>> problems_;
175
176 size_t current_controller_ = 0;
177 double solve_time_ = 0.0;
178};
179
180// Class to handle transitioning between both the profiled subsystem and the MPC
181// for shooting.
182class Catapult {
183 public:
184 Catapult(const constants::Values &values)
185 : catapult_(values.catapult.subsystem_params), catapult_mpc_(35) {}
186
187 using PotAndAbsoluteEncoderSubsystem =
188 ::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystem<
189 ::frc971::zeroing::PotAndAbsoluteEncoderZeroingEstimator,
190 ::frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus>;
191
192 // Resets all state for when WPILib restarts.
193 void Reset() { catapult_.Reset(); }
194
Ravago Jones5da06352022-03-04 20:26:24 -0800195 void Estop() { catapult_.Estop(); }
196
Austin Schuh39f26f62022-02-24 21:34:46 -0800197 bool zeroed() const { return catapult_.zeroed(); }
198 bool estopped() const { return catapult_.estopped(); }
199 double solve_time() const { return catapult_mpc_.solve_time(); }
200
201 bool mpc_active() const { return !use_profile_; }
202
Austin Schuh80fc2752022-02-25 13:33:56 -0800203 // Returns the number of shots taken.
204 int shot_count() const { return shot_count_; }
205
Ravago Jones5da06352022-03-04 20:26:24 -0800206 // Returns the estimated position
207 double estimated_position() const { return catapult_.estimated_position(); }
208
Austin Schuh39f26f62022-02-24 21:34:46 -0800209 // Runs either the MPC or the profiled subsystem depending on if we are
210 // shooting or not. Returns the status.
211 const flatbuffers::Offset<
212 frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus>
213 Iterate(const Goal *unsafe_goal, const Position *position,
Ravago Jones5da06352022-03-04 20:26:24 -0800214 double *catapult_voltage, bool fire,
215 flatbuffers::FlatBufferBuilder *fbb);
Austin Schuh39f26f62022-02-24 21:34:46 -0800216
217 private:
218 // TODO(austin): Prototype is just an encoder. Catapult has both an encoder
219 // and pot. Switch back once we have a catapult.
220 // PotAndAbsoluteEncoderSubsystem catapult_;
221 PotAndAbsoluteEncoderSubsystem catapult_;
222
223 catapult::CatapultController catapult_mpc_;
224
225 enum CatapultState { PROFILE, FIRING, RESETTING };
226
227 CatapultState catapult_state_ = CatapultState::PROFILE;
228
229 bool last_firing_ = false;
230 bool use_profile_ = true;
Austin Schuh80fc2752022-02-25 13:33:56 -0800231
232 int shot_count_ = 0;
Austin Schuh39f26f62022-02-24 21:34:46 -0800233};
234
Austin Schuh1d731362022-02-22 13:57:55 -0800235} // namespace catapult
236} // namespace superstructure
237} // namespace control_loops
238} // namespace y2022
239
240#endif // Y2022_CONTROL_LOOPS_SUPERSTRUCTURE_CATAPULT_CATAPULT_H_