blob: 64b4612ed0448f5525fcb5f1cb305390366578db [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"
6#include "aos/logging/matrix_logging.h"
7#include "frc971/control_loops/dlqr.h"
8#include "frc971/control_loops/c2d.h"
9#include "frc971/control_loops/drivetrain/distance_spline.h"
10#include "frc971/control_loops/drivetrain/drivetrain_config.h"
11#include "frc971/control_loops/hybrid_state_feedback_loop.h"
12#include "frc971/control_loops/state_feedback_loop.h"
13
14namespace frc971 {
15namespace control_loops {
16namespace drivetrain {
17
18Trajectory::Trajectory(const DistanceSpline *spline,
19 const DrivetrainConfig<double> &config, double vmax,
20 int num_distance)
21 : spline_(spline),
22 velocity_drivetrain_(
23 ::std::unique_ptr<StateFeedbackLoop<2, 2, 2, double,
24 StateFeedbackHybridPlant<2, 2, 2>,
25 HybridKalman<2, 2, 2>>>(
26 new StateFeedbackLoop<2, 2, 2, double,
27 StateFeedbackHybridPlant<2, 2, 2>,
28 HybridKalman<2, 2, 2>>(
29 config.make_hybrid_drivetrain_velocity_loop()))),
30 robot_radius_l_(config.robot_radius),
31 robot_radius_r_(config.robot_radius),
32 longitudal_acceleration_(3.0),
33 lateral_acceleration_(2.0),
34 Tlr_to_la_((::Eigen::Matrix<double, 2, 2>() << 0.5, 0.5,
35 -1.0 / (robot_radius_l_ + robot_radius_r_),
36 1.0 / (robot_radius_l_ + robot_radius_r_))
37 .finished()),
38 Tla_to_lr_(Tlr_to_la_.inverse()),
Austin Schuhe73a9052019-01-07 12:16:17 -080039 plan_(num_distance == 0
40 ? ::std::max(100, static_cast<int>(spline_->length() / 0.0025))
41 : num_distance,
42 vmax),
43 plan_segment_type_(plan_.size() - 1, SegmentType::VELOCITY_LIMITED) {}
Austin Schuhec7f06d2019-01-04 07:47:15 +110044
45void Trajectory::LateralAccelPass() {
46 for (size_t i = 0; i < plan_.size(); ++i) {
47 const double distance = Distance(i);
48 plan_[i] = ::std::min(plan_[i], LateralVelocityCurvature(distance));
49 }
50}
51
52// TODO(austin): Deduplicate this potentially with the backward accel function.
53// Need to sort out how the max velocity limit is going to work since the
54// velocity and acceleration need to match at all points.
55// TODO(austin): Accel check the wheels instead of the center of mass.
56double Trajectory::ForwardAcceleration(const double x, const double v) {
57 ::Eigen::Matrix<double, 2, 1> K3;
58 ::Eigen::Matrix<double, 2, 1> K4;
59 ::Eigen::Matrix<double, 2, 1> K5;
60 K345(x, &K3, &K4, &K5);
61
62 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
63 // Now, solve for all a's and find the best one which meets our criteria.
64 double maxa = -::std::numeric_limits<double>::infinity();
65 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
66 (voltage_limit_ - C(1, 0)) / K5(1, 0),
67 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
68 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
69 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
70 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
71 maxa = ::std::max(maxa, a);
72 }
73 }
74
75 // Then, assume an acceleration oval and stay inside it.
76 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -080077 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
78 const double ellipse_width_stretch = ::std::sqrt(
79 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
80 ellipse_down_shift),
81 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +110082 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -080083 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
84 ellipse_width_stretch,
85 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +110086 // If we would end up with an imaginary number, cap us at 0 acceleration.
87 // TODO(austin): Investigate when this happens, why, and fix it.
88 if (squared < 0.0) {
Austin Schuhcc7e4762019-01-07 17:03:14 -080089 LOG(ERROR, "Imaginary %f, maxa: %f, fa(%f, %f) -> 0.0\n", squared, maxa, x,
90 v);
Austin Schuhec7f06d2019-01-04 07:47:15 +110091 return 0.0;
92 }
93 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -080094 ::std::sqrt(::std::abs(squared)) *
95 (longitudal_acceleration_ + ellipse_down_shift) -
96 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +110097 return ::std::min(longitudal_acceleration, maxa);
98}
99
100void Trajectory::ForwardPass() {
101 plan_[0] = 0.0;
102 const double delta_distance = Distance(1) - Distance(0);
103 for (size_t i = 0; i < plan_.size() - 1; ++i) {
104 const double distance = Distance(i);
105
106 // Integrate our acceleration forward one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800107 const double new_plan_velocity = IntegrateAccelForDistance(
108 [this](double x, double v) { return ForwardAcceleration(x, v); },
109 plan_[i], distance, delta_distance);
110
111 if (new_plan_velocity < plan_[i + 1]) {
112 plan_[i + 1] = new_plan_velocity;
113 plan_segment_type_[i] = SegmentType::ACCELERATION_LIMITED;
114 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100115 }
116}
117
118double Trajectory::BackwardAcceleration(double x, double v) {
119 ::Eigen::Matrix<double, 2, 1> K3;
120 ::Eigen::Matrix<double, 2, 1> K4;
121 ::Eigen::Matrix<double, 2, 1> K5;
122 K345(x, &K3, &K4, &K5);
123
124 // Now, solve for all a's and find the best one which meets our criteria.
125 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
126 double mina = ::std::numeric_limits<double>::infinity();
127 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
128 (voltage_limit_ - C(1, 0)) / K5(1, 0),
129 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
130 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
131 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
132 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
133 mina = ::std::min(mina, a);
134 }
135 }
136
137 // Then, assume an acceleration oval and stay inside it.
138 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -0800139 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
140 const double ellipse_width_stretch = ::std::sqrt(
141 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
142 ellipse_down_shift),
143 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100144 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800145 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
146 ellipse_width_stretch,
147 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100148 // If we would end up with an imaginary number, cap us at 0 acceleration.
149 // TODO(austin): Investigate when this happens, why, and fix it.
150 if (squared < 0.0) {
Austin Schuhcc7e4762019-01-07 17:03:14 -0800151 LOG(ERROR, "Imaginary %f, mina: %f, fa(%f, %f) -> 0.0\n", squared, mina, x,
152 v);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100153 return 0.0;
154 }
155 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800156 -::std::sqrt(::std::abs(squared)) *
157 (longitudal_acceleration_ + ellipse_down_shift) +
158 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100159 return ::std::max(longitudal_acceleration, mina);
160}
161
162void Trajectory::BackwardPass() {
163 const double delta_distance = Distance(0) - Distance(1);
164 plan_.back() = 0.0;
165 for (size_t i = plan_.size() - 1; i > 0; --i) {
166 const double distance = Distance(i);
167
168 // Integrate our deceleration back one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800169 const double new_plan_velocity = IntegrateAccelForDistance(
170 [this](double x, double v) { return BackwardAcceleration(x, v); },
171 plan_[i], distance, delta_distance);
172
173 if (new_plan_velocity < plan_[i - 1]) {
174 plan_[i - 1] = new_plan_velocity;
175 plan_segment_type_[i - 1] = SegmentType::DECELERATION_LIMITED;
176 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100177 }
178}
179
180::Eigen::Matrix<double, 3, 1> Trajectory::FFAcceleration(double distance) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800181 if (distance < 0.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100182 // Make sure we don't end up off the beginning of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800183 distance = 0.0;
184 } else if (distance > length()) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100185 // Make sure we don't end up off the end of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800186 distance = length();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100187 }
Austin Schuhe73a9052019-01-07 12:16:17 -0800188 const size_t before_index = DistanceToSegment(distance);
189 const size_t after_index = before_index + 1;
190
Austin Schuhec7f06d2019-01-04 07:47:15 +1100191 const double before_distance = Distance(before_index);
192 const double after_distance = Distance(after_index);
193
Austin Schuhec7f06d2019-01-04 07:47:15 +1100194 // And then also make sure we aren't curvature limited.
195 const double vcurvature = LateralVelocityCurvature(distance);
196
197 double acceleration;
198 double velocity;
Austin Schuhe73a9052019-01-07 12:16:17 -0800199 switch (plan_segment_type_[DistanceToSegment(distance)]) {
200 case SegmentType::VELOCITY_LIMITED:
201 acceleration = 0.0;
202 velocity = (plan_[before_index] + plan_[after_index]) / 2.0;
203 // TODO(austin): Accelerate or decelerate until we hit the limit in the
204 // time slice. Otherwise our acceleration will be lying for this slice.
205 // Do note, we've got small slices so the effect will be small.
206 break;
207 case SegmentType::CURVATURE_LIMITED:
208 velocity = vcurvature;
209 acceleration = 0.0;
210 break;
211 case SegmentType::ACCELERATION_LIMITED:
212 velocity = IntegrateAccelForDistance(
213 [this](double x, double v) { return ForwardAcceleration(x, v); },
214 plan_[before_index], before_distance, distance - before_distance);
215 acceleration = ForwardAcceleration(distance, velocity);
216 break;
217 case SegmentType::DECELERATION_LIMITED:
218 velocity = IntegrateAccelForDistance(
219 [this](double x, double v) { return BackwardAcceleration(x, v); },
220 plan_[after_index], after_distance, distance - after_distance);
221 acceleration = BackwardAcceleration(distance, velocity);
222 break;
223 default:
224 LOG(FATAL, "Unknown segment type %d\n",
225 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 Schuhe73a9052019-01-07 12:16:17 -0800232 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 {
262
263 const double sintheta = ::std::sin(state(2));
264 const double costheta = ::std::cos(state(2));
265 const ::Eigen::Matrix<double, 2, 1> linear_angular =
266 Tlr_to_la_ * state.block<2, 1>(3, 0);
267
268 // When stopped, just roll with a min velocity.
269 double linear_velocity = 0.0;
270 constexpr double kMinVelocity = 0.1;
271 if (::std::abs(linear_angular(0)) < kMinVelocity / 100.0) {
272 linear_velocity = 0.1;
273 } else if (::std::abs(linear_angular(0)) > kMinVelocity) {
274 linear_velocity = linear_angular(0);
275 } else if (linear_angular(0) > 0) {
276 linear_velocity = kMinVelocity;
277 } else if (linear_angular(0) < 0) {
278 linear_velocity = -kMinVelocity;
279 }
280
281 ::Eigen::Matrix<double, 5, 5> result = ::Eigen::Matrix<double, 5, 5>::Zero();
282 result(0, 2) = -sintheta * linear_velocity;
283 result(0, 3) = 0.5 * costheta;
284 result(0, 4) = 0.5 * costheta;
285
286 result(1, 2) = costheta * linear_velocity;
287 result(1, 3) = 0.5 * sintheta;
288 result(1, 4) = 0.5 * sintheta;
289
290 result(2, 3) = Tlr_to_la_(1, 0);
291 result(2, 4) = Tlr_to_la_(1, 1);
292
293 result.block<2, 2>(3, 3) =
294 velocity_drivetrain_->plant().coefficients().A_continuous;
295 return result;
296}
297
298::Eigen::Matrix<double, 5, 2> Trajectory::BLinearizedContinuous() const {
299 ::Eigen::Matrix<double, 5, 2> result = ::Eigen::Matrix<double, 5, 2>::Zero();
300 result.block<2, 2>(3, 0) =
301 velocity_drivetrain_->plant().coefficients().B_continuous;
302 return result;
303}
304
305void Trajectory::AB(const ::Eigen::Matrix<double, 5, 1> &state,
306 ::std::chrono::nanoseconds dt,
307 ::Eigen::Matrix<double, 5, 5> *A,
308 ::Eigen::Matrix<double, 5, 2> *B) const {
309 ::Eigen::Matrix<double, 5, 5> A_linearized_continuous =
310 ALinearizedContinuous(state);
311 ::Eigen::Matrix<double, 5, 2> B_linearized_continuous =
312 BLinearizedContinuous();
313
314 // Now, convert it to discrete.
315 controls::C2D(A_linearized_continuous, B_linearized_continuous,
316 dt, A, B);
317}
318
319::Eigen::Matrix<double, 2, 5> Trajectory::KForState(
320 const ::Eigen::Matrix<double, 5, 1> &state, ::std::chrono::nanoseconds dt,
321 const ::Eigen::DiagonalMatrix<double, 5> &Q,
322 const ::Eigen::DiagonalMatrix<double, 2> &R) const {
323 ::Eigen::Matrix<double, 5, 5> A;
324 ::Eigen::Matrix<double, 5, 2> B;
325 AB(state, dt, &A, &B);
326
327 ::Eigen::Matrix<double, 5, 5> S = ::Eigen::Matrix<double, 5, 5>::Zero();
328 ::Eigen::Matrix<double, 2, 5> K = ::Eigen::Matrix<double, 2, 5>::Zero();
329
330 int info = ::frc971::controls::dlqr<5, 2>(A, B, Q, R, &K, &S);
331 if (info == 0) {
332 LOG_MATRIX(INFO, "K", K);
333 } else {
334 LOG(ERROR, "Failed to solve %d, controllability: %d\n", info,
335 controls::Controllability(A, B));
336 // TODO(austin): Can we be more clever here? Use the last one? We should
337 // collect more info about when this breaks down from logs.
338 K = ::Eigen::Matrix<double, 2, 5>::Zero();
339 }
340 ::Eigen::EigenSolver<::Eigen::Matrix<double, 5, 5>> eigensolver(A - B * K);
341 const auto eigenvalues = eigensolver.eigenvalues();
342 LOG(DEBUG,
343 "Eigenvalues: (%f + %fj), (%f + %fj), (%f + %fj), (%f + %fj), (%f + "
344 "%fj)\n",
345 eigenvalues(0).real(), eigenvalues(0).imag(), eigenvalues(1).real(),
346 eigenvalues(1).imag(), eigenvalues(2).real(), eigenvalues(2).imag(),
347 eigenvalues(3).real(), eigenvalues(3).imag(), eigenvalues(4).real(),
348 eigenvalues(4).imag());
349 return K;
350}
351
352const ::Eigen::Matrix<double, 5, 1> Trajectory::GoalState(double distance,
353 double velocity) {
354 ::Eigen::Matrix<double, 5, 1> result;
355 result.block<2, 1>(0, 0) = spline_->XY(distance);
356 result(2, 0) = spline_->Theta(distance);
357
358 result.block<2, 1>(3, 0) = Tla_to_lr_ *
359 (::Eigen::Matrix<double, 2, 1>() << velocity,
360 spline_->DThetaDt(distance, velocity))
361 .finished();
362 return result;
363}
364
365::std::vector<::Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
366 ::std::chrono::nanoseconds dt) {
367 double dt_float =
368 ::std::chrono::duration_cast<::std::chrono::duration<double>>(dt).count();
369 double t = 0.0;
370 ::Eigen::Matrix<double, 2, 1> state = ::Eigen::Matrix<double, 2, 1>::Zero();
371
372 ::std::vector<::Eigen::Matrix<double, 3, 1>> result;
373 result.emplace_back(FFAcceleration(0));
374 result.back()(1) = 0.0;
375
376 while (state(0) < length() - 1e-4) {
377 t += dt_float;
378
379 // TODO(austin): This feels like something that should be pulled out into
380 // a library for re-use.
381 state = RungeKutta(
382 [this](const ::Eigen::Matrix<double, 2, 1> x) {
383 ::Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
384 return (::Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
385 },
386 state, dt_float);
387
388 result.emplace_back(FFAcceleration(state(0)));
389 state(1) = result.back()(1);
390 }
391 return result;
392}
393
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800394void Trajectory::LimitVelocity(double starting_distance, double ending_distance,
395 const double max_velocity) {
396 const double segment_length = ending_distance - starting_distance;
397
398 const double min_length = length() / static_cast<double>(plan_.size() - 1);
399 if (starting_distance > ending_distance) {
400 LOG(FATAL, "End before start: %f > %f\n", starting_distance,
401 ending_distance);
402 }
403 starting_distance = ::std::min(length(), ::std::max(0.0, starting_distance));
404 ending_distance = ::std::min(length(), ::std::max(0.0, ending_distance));
405 if (segment_length < min_length) {
406 const size_t plan_index = static_cast<size_t>(
407 ::std::round((starting_distance + ending_distance) / 2.0 / min_length));
408 if (max_velocity < plan_[plan_index]) {
409 plan_[plan_index] = max_velocity;
410 }
411 } else {
412 for (size_t i = DistanceToSegment(starting_distance) + 1;
413 i < DistanceToSegment(ending_distance) + 1; ++i) {
414 if (max_velocity < plan_[i]) {
415 plan_[i] = max_velocity;
416 if (i < DistanceToSegment(ending_distance)) {
417 plan_segment_type_[i] = SegmentType::VELOCITY_LIMITED;
418 }
419 }
420 }
421 }
422}
423
Austin Schuhec7f06d2019-01-04 07:47:15 +1100424} // namespace drivetrain
425} // namespace control_loops
426} // namespace frc971