blob: ba4c0c21cab8351e143c58ac048cb8971e1aad09 [file] [log] [blame]
Austin Schuhec7f06d2019-01-04 07:47:15 +11001#include "frc971/control_loops/drivetrain/trajectory.h"
2
3#include <chrono>
4
5#include "Eigen/Dense"
Austin Schuhec7f06d2019-01-04 07:47:15 +11006#include "frc971/control_loops/c2d.h"
James Kuszmaul651fc3f2019-05-15 21:14:25 -07007#include "frc971/control_loops/dlqr.h"
Austin Schuhec7f06d2019-01-04 07:47:15 +11008#include "frc971/control_loops/drivetrain/distance_spline.h"
9#include "frc971/control_loops/drivetrain/drivetrain_config.h"
10#include "frc971/control_loops/hybrid_state_feedback_loop.h"
11#include "frc971/control_loops/state_feedback_loop.h"
12
13namespace frc971 {
14namespace control_loops {
15namespace drivetrain {
16
17Trajectory::Trajectory(const DistanceSpline *spline,
18 const DrivetrainConfig<double> &config, double vmax,
19 int num_distance)
20 : spline_(spline),
21 velocity_drivetrain_(
22 ::std::unique_ptr<StateFeedbackLoop<2, 2, 2, double,
23 StateFeedbackHybridPlant<2, 2, 2>,
24 HybridKalman<2, 2, 2>>>(
25 new StateFeedbackLoop<2, 2, 2, double,
26 StateFeedbackHybridPlant<2, 2, 2>,
27 HybridKalman<2, 2, 2>>(
28 config.make_hybrid_drivetrain_velocity_loop()))),
29 robot_radius_l_(config.robot_radius),
30 robot_radius_r_(config.robot_radius),
31 longitudal_acceleration_(3.0),
32 lateral_acceleration_(2.0),
33 Tlr_to_la_((::Eigen::Matrix<double, 2, 2>() << 0.5, 0.5,
34 -1.0 / (robot_radius_l_ + robot_radius_r_),
35 1.0 / (robot_radius_l_ + robot_radius_r_))
36 .finished()),
37 Tla_to_lr_(Tlr_to_la_.inverse()),
Austin Schuhe73a9052019-01-07 12:16:17 -080038 plan_(num_distance == 0
39 ? ::std::max(100, static_cast<int>(spline_->length() / 0.0025))
40 : num_distance,
41 vmax),
42 plan_segment_type_(plan_.size() - 1, SegmentType::VELOCITY_LIMITED) {}
Austin Schuhec7f06d2019-01-04 07:47:15 +110043
44void Trajectory::LateralAccelPass() {
45 for (size_t i = 0; i < plan_.size(); ++i) {
46 const double distance = Distance(i);
47 plan_[i] = ::std::min(plan_[i], LateralVelocityCurvature(distance));
48 }
49}
50
51// TODO(austin): Deduplicate this potentially with the backward accel function.
52// Need to sort out how the max velocity limit is going to work since the
53// velocity and acceleration need to match at all points.
54// TODO(austin): Accel check the wheels instead of the center of mass.
55double Trajectory::ForwardAcceleration(const double x, const double v) {
56 ::Eigen::Matrix<double, 2, 1> K3;
57 ::Eigen::Matrix<double, 2, 1> K4;
58 ::Eigen::Matrix<double, 2, 1> K5;
59 K345(x, &K3, &K4, &K5);
60
61 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
62 // Now, solve for all a's and find the best one which meets our criteria.
63 double maxa = -::std::numeric_limits<double>::infinity();
64 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
65 (voltage_limit_ - C(1, 0)) / K5(1, 0),
66 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
67 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
68 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
69 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
70 maxa = ::std::max(maxa, a);
71 }
72 }
73
74 // Then, assume an acceleration oval and stay inside it.
75 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -080076 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
77 const double ellipse_width_stretch = ::std::sqrt(
78 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
79 ellipse_down_shift),
80 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +110081 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -080082 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
83 ellipse_width_stretch,
84 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +110085 // If we would end up with an imaginary number, cap us at 0 acceleration.
86 // TODO(austin): Investigate when this happens, why, and fix it.
87 if (squared < 0.0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -070088 AOS_LOG(ERROR, "Imaginary %f, maxa: %f, fa(%f, %f) -> 0.0\n", squared, maxa,
89 x, v);
Austin Schuhec7f06d2019-01-04 07:47:15 +110090 return 0.0;
91 }
92 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -080093 ::std::sqrt(::std::abs(squared)) *
94 (longitudal_acceleration_ + ellipse_down_shift) -
95 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +110096 return ::std::min(longitudal_acceleration, maxa);
97}
98
99void Trajectory::ForwardPass() {
100 plan_[0] = 0.0;
101 const double delta_distance = Distance(1) - Distance(0);
102 for (size_t i = 0; i < plan_.size() - 1; ++i) {
103 const double distance = Distance(i);
104
105 // Integrate our acceleration forward one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800106 const double new_plan_velocity = IntegrateAccelForDistance(
107 [this](double x, double v) { return ForwardAcceleration(x, v); },
108 plan_[i], distance, delta_distance);
109
110 if (new_plan_velocity < plan_[i + 1]) {
111 plan_[i + 1] = new_plan_velocity;
112 plan_segment_type_[i] = SegmentType::ACCELERATION_LIMITED;
113 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100114 }
115}
116
117double Trajectory::BackwardAcceleration(double x, double v) {
118 ::Eigen::Matrix<double, 2, 1> K3;
119 ::Eigen::Matrix<double, 2, 1> K4;
120 ::Eigen::Matrix<double, 2, 1> K5;
121 K345(x, &K3, &K4, &K5);
122
123 // Now, solve for all a's and find the best one which meets our criteria.
124 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
125 double mina = ::std::numeric_limits<double>::infinity();
126 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
127 (voltage_limit_ - C(1, 0)) / K5(1, 0),
128 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
129 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
130 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
131 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
132 mina = ::std::min(mina, a);
133 }
134 }
135
136 // Then, assume an acceleration oval and stay inside it.
137 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -0800138 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
139 const double ellipse_width_stretch = ::std::sqrt(
140 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
141 ellipse_down_shift),
142 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100143 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800144 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
145 ellipse_width_stretch,
146 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100147 // If we would end up with an imaginary number, cap us at 0 acceleration.
148 // TODO(austin): Investigate when this happens, why, and fix it.
149 if (squared < 0.0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700150 AOS_LOG(ERROR, "Imaginary %f, mina: %f, fa(%f, %f) -> 0.0\n", squared, mina,
151 x, v);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100152 return 0.0;
153 }
154 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800155 -::std::sqrt(::std::abs(squared)) *
156 (longitudal_acceleration_ + ellipse_down_shift) +
157 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100158 return ::std::max(longitudal_acceleration, mina);
159}
160
161void Trajectory::BackwardPass() {
162 const double delta_distance = Distance(0) - Distance(1);
163 plan_.back() = 0.0;
164 for (size_t i = plan_.size() - 1; i > 0; --i) {
165 const double distance = Distance(i);
166
167 // Integrate our deceleration back one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800168 const double new_plan_velocity = IntegrateAccelForDistance(
169 [this](double x, double v) { return BackwardAcceleration(x, v); },
170 plan_[i], distance, delta_distance);
171
172 if (new_plan_velocity < plan_[i - 1]) {
173 plan_[i - 1] = new_plan_velocity;
174 plan_segment_type_[i - 1] = SegmentType::DECELERATION_LIMITED;
175 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100176 }
177}
178
179::Eigen::Matrix<double, 3, 1> Trajectory::FFAcceleration(double distance) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800180 if (distance < 0.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100181 // Make sure we don't end up off the beginning of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800182 distance = 0.0;
183 } else if (distance > length()) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100184 // Make sure we don't end up off the end of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800185 distance = length();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100186 }
Austin Schuhe73a9052019-01-07 12:16:17 -0800187 const size_t before_index = DistanceToSegment(distance);
188 const size_t after_index = before_index + 1;
189
Austin Schuhec7f06d2019-01-04 07:47:15 +1100190 const double before_distance = Distance(before_index);
191 const double after_distance = Distance(after_index);
192
Austin Schuhec7f06d2019-01-04 07:47:15 +1100193 // And then also make sure we aren't curvature limited.
194 const double vcurvature = LateralVelocityCurvature(distance);
195
196 double acceleration;
197 double velocity;
Austin Schuhe73a9052019-01-07 12:16:17 -0800198 switch (plan_segment_type_[DistanceToSegment(distance)]) {
199 case SegmentType::VELOCITY_LIMITED:
200 acceleration = 0.0;
201 velocity = (plan_[before_index] + plan_[after_index]) / 2.0;
202 // TODO(austin): Accelerate or decelerate until we hit the limit in the
203 // time slice. Otherwise our acceleration will be lying for this slice.
204 // Do note, we've got small slices so the effect will be small.
205 break;
206 case SegmentType::CURVATURE_LIMITED:
207 velocity = vcurvature;
208 acceleration = 0.0;
209 break;
210 case SegmentType::ACCELERATION_LIMITED:
211 velocity = IntegrateAccelForDistance(
212 [this](double x, double v) { return ForwardAcceleration(x, v); },
213 plan_[before_index], before_distance, distance - before_distance);
214 acceleration = ForwardAcceleration(distance, velocity);
215 break;
216 case SegmentType::DECELERATION_LIMITED:
217 velocity = IntegrateAccelForDistance(
218 [this](double x, double v) { return BackwardAcceleration(x, v); },
219 plan_[after_index], after_distance, distance - after_distance);
220 acceleration = BackwardAcceleration(distance, velocity);
221 break;
222 default:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700223 AOS_LOG(
224 FATAL, "Unknown segment type %d\n",
Austin Schuhe73a9052019-01-07 12:16:17 -0800225 static_cast<int>(plan_segment_type_[DistanceToSegment(distance)]));
226 break;
227 }
228
229 if (vcurvature < velocity) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100230 velocity = vcurvature;
231 acceleration = 0.0;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700232 AOS_LOG(ERROR, "Curvature won\n");
Austin Schuhec7f06d2019-01-04 07:47:15 +1100233 }
234 return (::Eigen::Matrix<double, 3, 1>() << distance, velocity, acceleration)
235 .finished();
236}
237
238::Eigen::Matrix<double, 2, 1> Trajectory::FFVoltage(double distance) {
239 const Eigen::Matrix<double, 3, 1> xva = FFAcceleration(distance);
240 const double velocity = xva(1);
241 const double acceleration = xva(2);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100242
Austin Schuhe73a9052019-01-07 12:16:17 -0800243 ::Eigen::Matrix<double, 2, 1> K3;
244 ::Eigen::Matrix<double, 2, 1> K4;
245 ::Eigen::Matrix<double, 2, 1> K5;
246 K345(distance, &K3, &K4, &K5);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100247
248 return K5 * acceleration + K3 * velocity * velocity + K4 * velocity;
249}
250
251const ::std::vector<double> Trajectory::Distances() const {
252 ::std::vector<double> d;
253 d.reserve(plan_.size());
254 for (size_t i = 0; i < plan_.size(); ++i) {
255 d.push_back(Distance(i));
256 }
257 return d;
258}
259
260::Eigen::Matrix<double, 5, 5> Trajectory::ALinearizedContinuous(
261 const ::Eigen::Matrix<double, 5, 1> &state) const {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100262 const double sintheta = ::std::sin(state(2));
263 const double costheta = ::std::cos(state(2));
264 const ::Eigen::Matrix<double, 2, 1> linear_angular =
265 Tlr_to_la_ * state.block<2, 1>(3, 0);
266
267 // When stopped, just roll with a min velocity.
268 double linear_velocity = 0.0;
269 constexpr double kMinVelocity = 0.1;
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700270 if (::std::abs(linear_angular(0)) < kMinVelocity / 100.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100271 linear_velocity = 0.1;
272 } else if (::std::abs(linear_angular(0)) > kMinVelocity) {
273 linear_velocity = linear_angular(0);
274 } else if (linear_angular(0) > 0) {
275 linear_velocity = kMinVelocity;
276 } else if (linear_angular(0) < 0) {
277 linear_velocity = -kMinVelocity;
278 }
279
280 ::Eigen::Matrix<double, 5, 5> result = ::Eigen::Matrix<double, 5, 5>::Zero();
281 result(0, 2) = -sintheta * linear_velocity;
282 result(0, 3) = 0.5 * costheta;
283 result(0, 4) = 0.5 * costheta;
284
285 result(1, 2) = costheta * linear_velocity;
286 result(1, 3) = 0.5 * sintheta;
287 result(1, 4) = 0.5 * sintheta;
288
289 result(2, 3) = Tlr_to_la_(1, 0);
290 result(2, 4) = Tlr_to_la_(1, 1);
291
292 result.block<2, 2>(3, 3) =
293 velocity_drivetrain_->plant().coefficients().A_continuous;
294 return result;
295}
296
297::Eigen::Matrix<double, 5, 2> Trajectory::BLinearizedContinuous() const {
298 ::Eigen::Matrix<double, 5, 2> result = ::Eigen::Matrix<double, 5, 2>::Zero();
299 result.block<2, 2>(3, 0) =
300 velocity_drivetrain_->plant().coefficients().B_continuous;
301 return result;
302}
303
304void Trajectory::AB(const ::Eigen::Matrix<double, 5, 1> &state,
305 ::std::chrono::nanoseconds dt,
306 ::Eigen::Matrix<double, 5, 5> *A,
307 ::Eigen::Matrix<double, 5, 2> *B) const {
308 ::Eigen::Matrix<double, 5, 5> A_linearized_continuous =
309 ALinearizedContinuous(state);
310 ::Eigen::Matrix<double, 5, 2> B_linearized_continuous =
311 BLinearizedContinuous();
312
313 // Now, convert it to discrete.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700314 controls::C2D(A_linearized_continuous, B_linearized_continuous, dt, A, B);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100315}
316
317::Eigen::Matrix<double, 2, 5> Trajectory::KForState(
318 const ::Eigen::Matrix<double, 5, 1> &state, ::std::chrono::nanoseconds dt,
319 const ::Eigen::DiagonalMatrix<double, 5> &Q,
320 const ::Eigen::DiagonalMatrix<double, 2> &R) const {
321 ::Eigen::Matrix<double, 5, 5> A;
322 ::Eigen::Matrix<double, 5, 2> B;
323 AB(state, dt, &A, &B);
324
325 ::Eigen::Matrix<double, 5, 5> S = ::Eigen::Matrix<double, 5, 5>::Zero();
326 ::Eigen::Matrix<double, 2, 5> K = ::Eigen::Matrix<double, 2, 5>::Zero();
327
328 int info = ::frc971::controls::dlqr<5, 2>(A, B, Q, R, &K, &S);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700329 if (info != 0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700330 AOS_LOG(ERROR, "Failed to solve %d, controllability: %d\n", info,
331 controls::Controllability(A, B));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100332 // TODO(austin): Can we be more clever here? Use the last one? We should
333 // collect more info about when this breaks down from logs.
334 K = ::Eigen::Matrix<double, 2, 5>::Zero();
335 }
336 ::Eigen::EigenSolver<::Eigen::Matrix<double, 5, 5>> eigensolver(A - B * K);
337 const auto eigenvalues = eigensolver.eigenvalues();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700338 AOS_LOG(DEBUG,
339 "Eigenvalues: (%f + %fj), (%f + %fj), (%f + %fj), (%f + %fj), (%f + "
340 "%fj)\n",
341 eigenvalues(0).real(), eigenvalues(0).imag(), eigenvalues(1).real(),
342 eigenvalues(1).imag(), eigenvalues(2).real(), eigenvalues(2).imag(),
343 eigenvalues(3).real(), eigenvalues(3).imag(), eigenvalues(4).real(),
344 eigenvalues(4).imag());
Austin Schuhec7f06d2019-01-04 07:47:15 +1100345 return K;
346}
347
348const ::Eigen::Matrix<double, 5, 1> Trajectory::GoalState(double distance,
349 double velocity) {
350 ::Eigen::Matrix<double, 5, 1> result;
351 result.block<2, 1>(0, 0) = spline_->XY(distance);
352 result(2, 0) = spline_->Theta(distance);
353
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700354 result.block<2, 1>(3, 0) =
355 Tla_to_lr_ * (::Eigen::Matrix<double, 2, 1>() << velocity,
356 spline_->DThetaDt(distance, velocity))
357 .finished();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100358 return result;
359}
360
Alex Perry4ae2fd72019-02-03 15:55:57 -0800361::Eigen::Matrix<double, 3, 1> Trajectory::GetNextXVA(
362 ::std::chrono::nanoseconds dt, ::Eigen::Matrix<double, 2, 1> *state) {
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700363 double dt_float = ::aos::time::DurationInSeconds(dt);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100364
Alex Perry4ae2fd72019-02-03 15:55:57 -0800365 // TODO(austin): This feels like something that should be pulled out into
366 // a library for re-use.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700367 *state = RungeKutta(
368 [this](const ::Eigen::Matrix<double, 2, 1> x) {
369 ::Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
370 return (::Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
371 },
372 *state, dt_float);
Alex Perry4ae2fd72019-02-03 15:55:57 -0800373
374 ::Eigen::Matrix<double, 3, 1> result = FFAcceleration((*state)(0));
375 (*state)(1) = result(1);
376 return result;
377}
378
379::std::vector<::Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
380 ::std::chrono::nanoseconds dt) {
381 ::Eigen::Matrix<double, 2, 1> state = ::Eigen::Matrix<double, 2, 1>::Zero();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100382 ::std::vector<::Eigen::Matrix<double, 3, 1>> result;
383 result.emplace_back(FFAcceleration(0));
384 result.back()(1) = 0.0;
385
Alex Perry4ae2fd72019-02-03 15:55:57 -0800386 while (!is_at_end(state)) {
387 result.emplace_back(GetNextXVA(dt, &state));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100388 }
389 return result;
390}
391
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800392void Trajectory::LimitVelocity(double starting_distance, double ending_distance,
393 const double max_velocity) {
394 const double segment_length = ending_distance - starting_distance;
395
396 const double min_length = length() / static_cast<double>(plan_.size() - 1);
397 if (starting_distance > ending_distance) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700398 AOS_LOG(FATAL, "End before start: %f > %f\n", starting_distance,
399 ending_distance);
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800400 }
401 starting_distance = ::std::min(length(), ::std::max(0.0, starting_distance));
402 ending_distance = ::std::min(length(), ::std::max(0.0, ending_distance));
403 if (segment_length < min_length) {
404 const size_t plan_index = static_cast<size_t>(
405 ::std::round((starting_distance + ending_distance) / 2.0 / min_length));
406 if (max_velocity < plan_[plan_index]) {
407 plan_[plan_index] = max_velocity;
408 }
409 } else {
410 for (size_t i = DistanceToSegment(starting_distance) + 1;
411 i < DistanceToSegment(ending_distance) + 1; ++i) {
412 if (max_velocity < plan_[i]) {
413 plan_[i] = max_velocity;
414 if (i < DistanceToSegment(ending_distance)) {
415 plan_segment_type_[i] = SegmentType::VELOCITY_LIMITED;
416 }
417 }
418 }
419 }
420}
421
Austin Schuhec7f06d2019-01-04 07:47:15 +1100422} // namespace drivetrain
423} // namespace control_loops
424} // namespace frc971