blob: ae51cb18c7859a60a8c6731fa6339b4b2a3d9bcb [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"
Austin Schuhec7f06d2019-01-04 07:47:15 +11007#include "frc971/control_loops/c2d.h"
James Kuszmaul651fc3f2019-05-15 21:14:25 -07008#include "frc971/control_loops/dlqr.h"
Austin Schuhec7f06d2019-01-04 07:47:15 +11009#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 {
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);
329 if (info == 0) {
330 LOG_MATRIX(INFO, "K", K);
331 } else {
332 LOG(ERROR, "Failed to solve %d, controllability: %d\n", info,
333 controls::Controllability(A, B));
334 // TODO(austin): Can we be more clever here? Use the last one? We should
335 // collect more info about when this breaks down from logs.
336 K = ::Eigen::Matrix<double, 2, 5>::Zero();
337 }
338 ::Eigen::EigenSolver<::Eigen::Matrix<double, 5, 5>> eigensolver(A - B * K);
339 const auto eigenvalues = eigensolver.eigenvalues();
340 LOG(DEBUG,
341 "Eigenvalues: (%f + %fj), (%f + %fj), (%f + %fj), (%f + %fj), (%f + "
342 "%fj)\n",
343 eigenvalues(0).real(), eigenvalues(0).imag(), eigenvalues(1).real(),
344 eigenvalues(1).imag(), eigenvalues(2).real(), eigenvalues(2).imag(),
345 eigenvalues(3).real(), eigenvalues(3).imag(), eigenvalues(4).real(),
346 eigenvalues(4).imag());
347 return K;
348}
349
350const ::Eigen::Matrix<double, 5, 1> Trajectory::GoalState(double distance,
351 double velocity) {
352 ::Eigen::Matrix<double, 5, 1> result;
353 result.block<2, 1>(0, 0) = spline_->XY(distance);
354 result(2, 0) = spline_->Theta(distance);
355
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700356 result.block<2, 1>(3, 0) =
357 Tla_to_lr_ * (::Eigen::Matrix<double, 2, 1>() << velocity,
358 spline_->DThetaDt(distance, velocity))
359 .finished();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100360 return result;
361}
362
Alex Perry4ae2fd72019-02-03 15:55:57 -0800363::Eigen::Matrix<double, 3, 1> Trajectory::GetNextXVA(
364 ::std::chrono::nanoseconds dt, ::Eigen::Matrix<double, 2, 1> *state) {
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700365 double dt_float = ::aos::time::DurationInSeconds(dt);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100366
Alex Perry4ae2fd72019-02-03 15:55:57 -0800367 // TODO(austin): This feels like something that should be pulled out into
368 // a library for re-use.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700369 *state = RungeKutta(
370 [this](const ::Eigen::Matrix<double, 2, 1> x) {
371 ::Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
372 return (::Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
373 },
374 *state, dt_float);
Alex Perry4ae2fd72019-02-03 15:55:57 -0800375
376 ::Eigen::Matrix<double, 3, 1> result = FFAcceleration((*state)(0));
377 (*state)(1) = result(1);
378 return result;
379}
380
381::std::vector<::Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
382 ::std::chrono::nanoseconds dt) {
383 ::Eigen::Matrix<double, 2, 1> state = ::Eigen::Matrix<double, 2, 1>::Zero();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100384 ::std::vector<::Eigen::Matrix<double, 3, 1>> result;
385 result.emplace_back(FFAcceleration(0));
386 result.back()(1) = 0.0;
387
Alex Perry4ae2fd72019-02-03 15:55:57 -0800388 while (!is_at_end(state)) {
389 result.emplace_back(GetNextXVA(dt, &state));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100390 }
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