2#include "lemlib/util.hpp"
10LTVPathFollower::Vector2::Vector2(
float x,
float y) : x(x), y(y) {}
12std::string LTVPathFollower::Vector2::latex()
const {
13 std::ostringstream oss;
14 oss <<
"\\left(" << std::fixed << this->x <<
"," << std::fixed << this->y <<
"\\right)";
18double LTVPathFollower::angleError(
double robotAngle,
double targetAngle) {
19 constexpr double TWO_PI = 2.0 * M_PI;
20 double diff = std::fmod(targetAngle - robotAngle, TWO_PI);
21 if (diff < -M_PI) diff += TWO_PI;
22 else if (diff >= M_PI) diff -= TWO_PI;
26LTVPathFollower::LTVPathFollower(
const VelocityControllerConfig&
config)
39Eigen::MatrixXf LTVPathFollower::dareSolver(
const Eigen::MatrixXf &A,
const Eigen::MatrixXf &B,
const Eigen::MatrixXf &Q,
const Eigen::MatrixXf &R) {
40 Eigen::MatrixXf
X = Q;
41 Eigen::MatrixXf X_prev;
44 for (
int i = 0; i < 1000; ++i) {
46 K = (R + B.transpose() * X * B).inverse() * B.transpose() *
X * A;
47 X = A.transpose() *
X * (A - B * K) + Q;
49 if ((X - X_prev).norm() < 1e-4) {
56std::pair<Eigen::MatrixXf, Eigen::MatrixXf> LTVPathFollower::discretizeAB(
57 const Eigen::MatrixXf& contA,
const Eigen::MatrixXf& contB,
double dtSeconds) {
59 int states = contA.rows();
60 int inputs = contB.cols();
62 Eigen::MatrixXf M(states + inputs, states + inputs);
64 M.topLeftCorner(states, states) = contA;
65 M.topRightCorner(states, inputs) = contB;
67 Eigen::MatrixXf Mdt = M * dtSeconds;
68 Eigen::MatrixXf I = Eigen::MatrixXf::Identity(M.rows(), M.cols());
69 Eigen::MatrixXf M2 = Mdt * Mdt;
71 Eigen::MatrixXf phi = I + Mdt + (M2 / 2.0);
73 Eigen::MatrixXf discA = phi.topLeftCorner(states, states);
74 Eigen::MatrixXf discB = phi.topRightCorner(states, inputs);
76 return {discA, discB};
79void LTVPathFollower::precompute_paths(
const std::vector<std::string>& path_names) {
80 auto* stored =
new std::vector<std::string>(path_names);
81 pros::Task t(precompute_paths_task, stored);
84void LTVPathFollower::precompute_paths_task(
void* param) {
85 auto* path_names =
static_cast<std::vector<std::string>*
>(param);
86 precomputed_paths.clear();
87 precomputed_paths.reserve(path_names->size());
88 for (
const auto& name : *path_names) {
89 precomputed_paths.push_back(prepare_trajectory(name));
95std::vector<std::pair<double,double>> LTVPathFollower::parse_pairs(
const std::string& line) {
96 std::vector<std::pair<double,double>> result;
98 bool inside_parens =
false;
102 inside_parens =
true;
103 }
else if (c ==
')') {
104 std::replace(temp.begin(), temp.end(),
',',
' ');
105 std::istringstream ss(temp);
106 double first, second;
107 ss >> first >> second;
108 result.emplace_back(first, second);
109 inside_parens =
false;
110 }
else if (inside_parens) {
117std::vector<State> LTVPathFollower::prepare_trajectory(
const std::string& data) {
118 std::istringstream ss(data);
119 std::vector<std::pair<double,double>>
X, L, A;
122 while (std::getline(ss, line)) {
123 if (line.find(
"X =") != std::string::npos)
X = parse_pairs(line.substr(line.find(
'[')));
124 else if (line.find(
"L =") != std::string::npos) L = parse_pairs(line.substr(line.find(
'[')));
125 else if (line.find(
"A =") != std::string::npos) A = parse_pairs(line.substr(line.find(
'[')));
129 if (n == 0)
return {};
130 std::vector<State> states(n);
132 for (
size_t i = 0; i < n; i++) {
133 states[i].x =
X[i].first;
134 states[i].y =
X[i].second;
135 states[i].linear_vel = L[i].second;
136 states[i].angular_vel = A[i].second;
139 if (n > 1) states[0].heading = atan2(states[1].y - states[0].y, states[1].x - states[0].x);
140 for (
size_t i = 1; i < n - 1; i++) {
141 states[i].heading = atan2(states[i + 1].y - states[i - 1].y, states[i + 1].x - states[i - 1].x);
143 if (n > 1) states[n - 1].heading = atan2(states[n - 1].y - states[n - 2].y, states[n - 1].x - states[n - 2].x);
148void LTVPathFollower::followPath(
const std::string& path_name,
const ltvConfig& l_config) {
149 std::vector<State> trajectory;
151 if(l_config.path_index >= 0 && (
size_t)(l_config.path_index) < precomputed_paths.size()) {
152 if (!precomputed_paths.at(l_config.path_index).empty()) {
153 trajectory = precomputed_paths.at(l_config.path_index);
156 if (trajectory.empty()) trajectory = prepare_trajectory(path_name);
157 if (trajectory.empty())
return;
159 Eigen::Matrix3f Q_mat;
160 Q_mat << l_config.q_x, 0, 0,
162 0, 0, l_config.q_theta;
164 Eigen::Matrix2f R_mat;
165 R_mat << l_config.r_vel, 0,
170 }
else if(l_config.turnFirst) {
171 double targetH = lemlib::radToDeg(M_PI_2 - trajectory[0].heading);
172 chassis.turnToHeading(l_config.backwards ? targetH + 180 : targetH, 1000);
175 std::vector<std::string> logs;
176 int trajectory_size = trajectory.size();
180 for (
int i = 0; i < trajectory_size; ++i) {
181 uint32_t start_time_ms = pros::millis();
182 const auto &target_state = trajectory[i];
184 lemlib::Pose current_pose =
chassis.getPose(
true);
187 current_pose.theta = M_PI_2 - current_pose.theta;
189 double targetHeadingAdjusted = target_state.heading + (l_config.backwards ? M_PI : 0);
190 double errorTheta = angleError(current_pose.theta, targetHeadingAdjusted);
192 Eigen::Vector3d global_error;
193 global_error << target_state.x - current_pose.x, target_state.y - current_pose.y, errorTheta;
195 Eigen::Matrix3d rotation_matrix;
196 rotation_matrix << std::cos(current_pose.theta), std::sin(current_pose.theta), 0,
197 -std::sin(current_pose.theta), std::cos(current_pose.theta), 0,
200 Eigen::Vector3d error = rotation_matrix * global_error;
202 float v_ref = target_state.linear_vel * (l_config.backwards ? -1.0 : 1.0);
203 float w_ref = target_state.angular_vel;
205 float v_calc = v_ref;
206 if (std::abs(v_calc) < 0.25) {
207 v_calc = (v_calc >= 0) ? 0.25 : -0.25;
215 Eigen::Matrix<float, 3, 2> B;
220 auto discAB = discretizeAB(A, B, dt);
222 Eigen::MatrixXf
X = dareSolver(discAB.first, discAB.second, Q_mat, R_mat);
224 Eigen::MatrixXf K = (R_mat + discAB.second.transpose() * X * discAB.second).inverse() * discAB.second.transpose() *
X * discAB.first;
226 Eigen::Vector2f u = K * error.cast<
float>();
228 float v_cmd = v_ref + u(0);
229 float w_cmd = w_ref + u(1);
234 DrivetrainVoltages output_voltages =
controller.update(
241 rightMotors.move_voltage(output_voltages.rightVoltage * 1000.0);
242 leftMotors.move_voltage(output_voltages.leftVoltage * 1000.0);
245 std::ostringstream ss;
246 ss << Vector2(current_pose.x, current_pose.y).latex() <<
",";
247 logs.push_back(ss.str());
250 pros::Task::delay_until(&start_time_ms, 10);
253 if(!l_config.test && l_config.end_correction) {
258 lemlib::radToDeg(M_PI_2 - trajectory.back().heading),
260 {.lead = l_config.mpose_lead}
269 for (
const auto& line : logs) {
const VelocityControllerConfig config
pros::MotorGroup rightMotors
pros::MotorGroup leftMotors
pros::Controller controller
static constexpr float INCH_TO_METER
static constexpr float rpm_to_mps_factor