blob: 23859cd7a207db42d166b184940a8a07d13c6688 [file] [log] [blame]
Nathan Leongdd728002024-02-03 15:26:53 -08001#include "frc971/control_loops/catapult/mpc_problem.h"
2
3namespace frc971::control_loops::catapult {
4namespace chrono = std::chrono;
5
6namespace {
7osqp::OsqpInstance MakeInstance(
8 size_t horizon, Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P) {
9 osqp::OsqpInstance instance;
10 instance.objective_matrix = P.sparseView();
11
12 instance.constraint_matrix =
13 Eigen::SparseMatrix<double, Eigen::ColMajor, osqp::c_int>(horizon,
14 horizon);
15 instance.constraint_matrix.setIdentity();
16
17 instance.lower_bounds =
18 Eigen::Matrix<double, Eigen::Dynamic, 1>::Zero(horizon, 1);
19 instance.upper_bounds =
20 Eigen::Matrix<double, Eigen::Dynamic, 1>::Ones(horizon, 1) * 12.0;
21 return instance;
22}
23
24} // namespace
25MPCProblem::MPCProblem(size_t horizon,
26 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P,
27 Eigen::Matrix<double, Eigen::Dynamic, 1> accel_q,
28 Eigen::Matrix<double, 2, 2> Af,
29 Eigen::Matrix<double, Eigen::Dynamic, 2> final_q)
30 : horizon_(horizon),
31 accel_q_(std::move(accel_q)),
32 Af_(std::move(Af)),
33 final_q_(std::move(final_q)),
34 instance_(MakeInstance(horizon, std::move(P))) {
35 // Start with a representative problem.
36 Eigen::Matrix<double, 2, 1> X_initial(0.0, 0.0);
37 Eigen::Matrix<double, 2, 1> X_final(2.0, 25.0);
38
39 objective_vector_ =
40 X_initial(1, 0) * accel_q_ + final_q_ * (Af_ * X_initial - X_final);
41 instance_.objective_vector = objective_vector_;
42 settings_.max_iter = 25;
43 settings_.check_termination = 5;
44 settings_.warm_start = 1;
45 // TODO(austin): Do we need this scaling thing? It makes it not solve
46 // sometimes... I'm pretty certain by giving it a decently formed problem to
47 // initialize with, it will not try doing crazy things with the scaling
48 // internally.
49 settings_.scaling = 0;
50 auto status = solver_.Init(instance_, settings_);
51 CHECK(status.ok()) << status;
52}
53
54void MPCProblem::SetState(Eigen::Matrix<double, 2, 1> X_initial,
55 Eigen::Matrix<double, 2, 1> X_final) {
56 X_initial_ = X_initial;
57 X_final_ = X_final;
58 // If we mark this noalias(), it won't re-allocate the vector each time.
59 objective_vector_.noalias() =
60 X_initial(1, 0) * accel_q_ + final_q_ * (Af_ * X_initial - X_final);
61
62 auto status = solver_.SetObjectiveVector(objective_vector_);
63 CHECK(status.ok()) << status;
64}
65
66bool MPCProblem::Solve() {
67 const aos::monotonic_clock::time_point start_time =
68 aos::monotonic_clock::now();
69 osqp::OsqpExitCode exit_code = solver_.Solve();
70 const aos::monotonic_clock::time_point end_time = aos::monotonic_clock::now();
71 VLOG(1) << "OSQP solved in "
72 << std::chrono::duration<double>(end_time - start_time).count();
73 solve_time_ = std::chrono::duration<double>(end_time - start_time).count();
74 // TODO(austin): Dump the exit codes out as an enum for logging.
75 //
76 // TODO(austin): The dual problem doesn't appear to be converging on all
77 // problems. Are we phrasing something wrong?
78
79 // TODO(austin): Set a time limit so we can't run forever, and signal back
80 // when we hit our limit.
81 return exit_code == osqp::OsqpExitCode::kOptimal;
82}
83
84void MPCProblem::WarmStart(const MPCProblem &p) {
85 CHECK_GE(p.horizon(), horizon())
86 << ": Can only copy a bigger problem's solution into a smaller problem.";
87 auto status = solver_.SetPrimalWarmStart(p.solver_.primal_solution().block(
88 p.horizon() - horizon(), 0, horizon(), 1));
89 CHECK(status.ok()) << status;
90 status = solver_.SetDualWarmStart(p.solver_.dual_solution().block(
91 p.horizon() - horizon(), 0, horizon(), 1));
92 CHECK(status.ok()) << status;
93}
94
95} // namespace frc971::control_loops::catapult