blob: 511543dc5e33ad0b02b3e8f7a38fcbeff52c8796 [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()),
39 plan_(num_distance, vmax) {}
40
41void Trajectory::LateralAccelPass() {
42 for (size_t i = 0; i < plan_.size(); ++i) {
43 const double distance = Distance(i);
44 plan_[i] = ::std::min(plan_[i], LateralVelocityCurvature(distance));
45 }
46}
47
48// TODO(austin): Deduplicate this potentially with the backward accel function.
49// Need to sort out how the max velocity limit is going to work since the
50// velocity and acceleration need to match at all points.
51// TODO(austin): Accel check the wheels instead of the center of mass.
52double Trajectory::ForwardAcceleration(const double x, const double v) {
53 ::Eigen::Matrix<double, 2, 1> K3;
54 ::Eigen::Matrix<double, 2, 1> K4;
55 ::Eigen::Matrix<double, 2, 1> K5;
56 K345(x, &K3, &K4, &K5);
57
58 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
59 // Now, solve for all a's and find the best one which meets our criteria.
60 double maxa = -::std::numeric_limits<double>::infinity();
61 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
62 (voltage_limit_ - C(1, 0)) / K5(1, 0),
63 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
64 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
65 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
66 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
67 maxa = ::std::max(maxa, a);
68 }
69 }
70
71 // Then, assume an acceleration oval and stay inside it.
72 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -080073 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
74 const double ellipse_width_stretch = ::std::sqrt(
75 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
76 ellipse_down_shift),
77 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +110078 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -080079 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
80 ellipse_width_stretch,
81 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +110082 // If we would end up with an imaginary number, cap us at 0 acceleration.
83 // TODO(austin): Investigate when this happens, why, and fix it.
84 if (squared < 0.0) {
Austin Schuhcc7e4762019-01-07 17:03:14 -080085 LOG(ERROR, "Imaginary %f, maxa: %f, fa(%f, %f) -> 0.0\n", squared, maxa, x,
86 v);
Austin Schuhec7f06d2019-01-04 07:47:15 +110087 return 0.0;
88 }
89 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -080090 ::std::sqrt(::std::abs(squared)) *
91 (longitudal_acceleration_ + ellipse_down_shift) -
92 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +110093 return ::std::min(longitudal_acceleration, maxa);
94}
95
96void Trajectory::ForwardPass() {
97 plan_[0] = 0.0;
98 const double delta_distance = Distance(1) - Distance(0);
99 for (size_t i = 0; i < plan_.size() - 1; ++i) {
100 const double distance = Distance(i);
101
102 // Integrate our acceleration forward one step.
103 plan_[i + 1] = ::std::min(
104 plan_[i + 1],
105 IntegrateAccelForDistance(
106 [this](double x, double v) { return ForwardAcceleration(x, v); },
107 plan_[i], distance, delta_distance));
108 }
109}
110
111double Trajectory::BackwardAcceleration(double x, double v) {
112 ::Eigen::Matrix<double, 2, 1> K3;
113 ::Eigen::Matrix<double, 2, 1> K4;
114 ::Eigen::Matrix<double, 2, 1> K5;
115 K345(x, &K3, &K4, &K5);
116
117 // Now, solve for all a's and find the best one which meets our criteria.
118 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
119 double mina = ::std::numeric_limits<double>::infinity();
120 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
121 (voltage_limit_ - C(1, 0)) / K5(1, 0),
122 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
123 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
124 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
125 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
126 mina = ::std::min(mina, a);
127 }
128 }
129
130 // Then, assume an acceleration oval and stay inside it.
131 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
Austin Schuhcc7e4762019-01-07 17:03:14 -0800132 const double ellipse_down_shift = longitudal_acceleration_ * 1.0;
133 const double ellipse_width_stretch = ::std::sqrt(
134 1.0 / (1.0 - ::std::pow(ellipse_down_shift / (longitudal_acceleration_ +
135 ellipse_down_shift),
136 2.0)));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100137 const double squared =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800138 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_ /
139 ellipse_width_stretch,
140 2.0);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100141 // If we would end up with an imaginary number, cap us at 0 acceleration.
142 // TODO(austin): Investigate when this happens, why, and fix it.
143 if (squared < 0.0) {
Austin Schuhcc7e4762019-01-07 17:03:14 -0800144 LOG(ERROR, "Imaginary %f, mina: %f, fa(%f, %f) -> 0.0\n", squared, mina, x,
145 v);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100146 return 0.0;
147 }
148 const double longitudal_acceleration =
Austin Schuhcc7e4762019-01-07 17:03:14 -0800149 -::std::sqrt(::std::abs(squared)) *
150 (longitudal_acceleration_ + ellipse_down_shift) +
151 ellipse_down_shift;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100152 return ::std::max(longitudal_acceleration, mina);
153}
154
155void Trajectory::BackwardPass() {
156 const double delta_distance = Distance(0) - Distance(1);
157 plan_.back() = 0.0;
158 for (size_t i = plan_.size() - 1; i > 0; --i) {
159 const double distance = Distance(i);
160
161 // Integrate our deceleration back one step.
162 plan_[i - 1] = ::std::min(
163 plan_[i - 1],
164 IntegrateAccelForDistance(
165 [this](double x, double v) { return BackwardAcceleration(x, v); },
166 plan_[i], distance, delta_distance));
167 }
168}
169
170::Eigen::Matrix<double, 3, 1> Trajectory::FFAcceleration(double distance) {
171 size_t before_index;
172 size_t after_index;
173 if (distance < Distance(1)) {
174 // Within the first step.
175 after_index = 1;
176 // Make sure we don't end up off the beginning of the curve.
177 if (distance < 0.0) {
178 distance = 0.0;
179 }
180 } else if (distance > Distance(plan_.size() - 2)) {
181 // Within the last step.
182 after_index = plan_.size() - 1;
183 // Make sure we don't end up off the end of the curve.
184 if (distance > length()) {
185 distance = length();
186 }
187 } else {
188 // Otherwise do the calculation normally.
189 after_index = static_cast<size_t>(
190 ::std::ceil(distance / length() * (plan_.size() - 1)));
191 }
192 before_index = after_index - 1;
193 const double before_distance = Distance(before_index);
194 const double after_distance = Distance(after_index);
195
196 // Now, compute the velocity that we could have if we accelerated from the
197 // previous step and decelerated from the next step. The min will tell us
198 // which is in effect.
199 const double velocity_forwards = IntegrateAccelForDistance(
200 [this](double x, double v) { return ForwardAcceleration(x, v); },
201 plan_[before_index], before_distance, distance - before_distance);
202 const double velocity_backward = IntegrateAccelForDistance(
203 [this](double x, double v) { return BackwardAcceleration(x, v); },
204 plan_[after_index], after_distance, distance - after_distance);
205
206 // And then also make sure we aren't curvature limited.
207 const double vcurvature = LateralVelocityCurvature(distance);
208
209 double acceleration;
210 double velocity;
211 if (vcurvature < velocity_forwards && vcurvature < velocity_backward) {
212 // If we are curvature limited, we can't accelerate.
213 velocity = vcurvature;
214 acceleration = 0.0;
215 } else if (velocity_forwards < velocity_backward) {
216 // Otherwise, pick the acceleration and velocity from the forward pass if it
217 // was the predominate factor in this step.
218 velocity = velocity_forwards;
219 acceleration = ForwardAcceleration(distance, velocity);
220 } else {
221 // Otherwise, pick the acceleration and velocity from the backward pass if
222 // it was the predominate factor in this step.
223 velocity = velocity_backward;
224 acceleration = BackwardAcceleration(distance, velocity);
225 }
226 return (::Eigen::Matrix<double, 3, 1>() << distance, velocity, acceleration)
227 .finished();
228}
229
230::Eigen::Matrix<double, 2, 1> Trajectory::FFVoltage(double distance) {
231 const Eigen::Matrix<double, 3, 1> xva = FFAcceleration(distance);
232 const double velocity = xva(1);
233 const double acceleration = xva(2);
234 const double current_ddtheta = spline_->DDTheta(distance);
235 const double current_dtheta = spline_->DTheta(distance);
236 // We've now got the equation:
237 // K2 * d^x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
238 const ::Eigen::Matrix<double, 2, 1> my_K2 = K2(current_dtheta);
239
240 const ::Eigen::Matrix<double, 2, 2> B_inverse =
241 velocity_drivetrain_->plant().coefficients().B_continuous.inverse();
242
243 // Now, rephrase it as K5 a + K3 v^2 + K4 v = U
244 const ::Eigen::Matrix<double, 2, 1> K5 = B_inverse * my_K2;
245 const ::Eigen::Matrix<double, 2, 1> K3 = B_inverse * K1(current_ddtheta);
246 const ::Eigen::Matrix<double, 2, 1> K4 =
247 -B_inverse * velocity_drivetrain_->plant().coefficients().A_continuous *
248 my_K2;
249
250 return K5 * acceleration + K3 * velocity * velocity + K4 * velocity;
251}
252
253const ::std::vector<double> Trajectory::Distances() const {
254 ::std::vector<double> d;
255 d.reserve(plan_.size());
256 for (size_t i = 0; i < plan_.size(); ++i) {
257 d.push_back(Distance(i));
258 }
259 return d;
260}
261
262::Eigen::Matrix<double, 5, 5> Trajectory::ALinearizedContinuous(
263 const ::Eigen::Matrix<double, 5, 1> &state) const {
264
265 const double sintheta = ::std::sin(state(2));
266 const double costheta = ::std::cos(state(2));
267 const ::Eigen::Matrix<double, 2, 1> linear_angular =
268 Tlr_to_la_ * state.block<2, 1>(3, 0);
269
270 // When stopped, just roll with a min velocity.
271 double linear_velocity = 0.0;
272 constexpr double kMinVelocity = 0.1;
273 if (::std::abs(linear_angular(0)) < kMinVelocity / 100.0) {
274 linear_velocity = 0.1;
275 } else if (::std::abs(linear_angular(0)) > kMinVelocity) {
276 linear_velocity = linear_angular(0);
277 } else if (linear_angular(0) > 0) {
278 linear_velocity = kMinVelocity;
279 } else if (linear_angular(0) < 0) {
280 linear_velocity = -kMinVelocity;
281 }
282
283 ::Eigen::Matrix<double, 5, 5> result = ::Eigen::Matrix<double, 5, 5>::Zero();
284 result(0, 2) = -sintheta * linear_velocity;
285 result(0, 3) = 0.5 * costheta;
286 result(0, 4) = 0.5 * costheta;
287
288 result(1, 2) = costheta * linear_velocity;
289 result(1, 3) = 0.5 * sintheta;
290 result(1, 4) = 0.5 * sintheta;
291
292 result(2, 3) = Tlr_to_la_(1, 0);
293 result(2, 4) = Tlr_to_la_(1, 1);
294
295 result.block<2, 2>(3, 3) =
296 velocity_drivetrain_->plant().coefficients().A_continuous;
297 return result;
298}
299
300::Eigen::Matrix<double, 5, 2> Trajectory::BLinearizedContinuous() const {
301 ::Eigen::Matrix<double, 5, 2> result = ::Eigen::Matrix<double, 5, 2>::Zero();
302 result.block<2, 2>(3, 0) =
303 velocity_drivetrain_->plant().coefficients().B_continuous;
304 return result;
305}
306
307void Trajectory::AB(const ::Eigen::Matrix<double, 5, 1> &state,
308 ::std::chrono::nanoseconds dt,
309 ::Eigen::Matrix<double, 5, 5> *A,
310 ::Eigen::Matrix<double, 5, 2> *B) const {
311 ::Eigen::Matrix<double, 5, 5> A_linearized_continuous =
312 ALinearizedContinuous(state);
313 ::Eigen::Matrix<double, 5, 2> B_linearized_continuous =
314 BLinearizedContinuous();
315
316 // Now, convert it to discrete.
317 controls::C2D(A_linearized_continuous, B_linearized_continuous,
318 dt, A, B);
319}
320
321::Eigen::Matrix<double, 2, 5> Trajectory::KForState(
322 const ::Eigen::Matrix<double, 5, 1> &state, ::std::chrono::nanoseconds dt,
323 const ::Eigen::DiagonalMatrix<double, 5> &Q,
324 const ::Eigen::DiagonalMatrix<double, 2> &R) const {
325 ::Eigen::Matrix<double, 5, 5> A;
326 ::Eigen::Matrix<double, 5, 2> B;
327 AB(state, dt, &A, &B);
328
329 ::Eigen::Matrix<double, 5, 5> S = ::Eigen::Matrix<double, 5, 5>::Zero();
330 ::Eigen::Matrix<double, 2, 5> K = ::Eigen::Matrix<double, 2, 5>::Zero();
331
332 int info = ::frc971::controls::dlqr<5, 2>(A, B, Q, R, &K, &S);
333 if (info == 0) {
334 LOG_MATRIX(INFO, "K", K);
335 } else {
336 LOG(ERROR, "Failed to solve %d, controllability: %d\n", info,
337 controls::Controllability(A, B));
338 // TODO(austin): Can we be more clever here? Use the last one? We should
339 // collect more info about when this breaks down from logs.
340 K = ::Eigen::Matrix<double, 2, 5>::Zero();
341 }
342 ::Eigen::EigenSolver<::Eigen::Matrix<double, 5, 5>> eigensolver(A - B * K);
343 const auto eigenvalues = eigensolver.eigenvalues();
344 LOG(DEBUG,
345 "Eigenvalues: (%f + %fj), (%f + %fj), (%f + %fj), (%f + %fj), (%f + "
346 "%fj)\n",
347 eigenvalues(0).real(), eigenvalues(0).imag(), eigenvalues(1).real(),
348 eigenvalues(1).imag(), eigenvalues(2).real(), eigenvalues(2).imag(),
349 eigenvalues(3).real(), eigenvalues(3).imag(), eigenvalues(4).real(),
350 eigenvalues(4).imag());
351 return K;
352}
353
354const ::Eigen::Matrix<double, 5, 1> Trajectory::GoalState(double distance,
355 double velocity) {
356 ::Eigen::Matrix<double, 5, 1> result;
357 result.block<2, 1>(0, 0) = spline_->XY(distance);
358 result(2, 0) = spline_->Theta(distance);
359
360 result.block<2, 1>(3, 0) = Tla_to_lr_ *
361 (::Eigen::Matrix<double, 2, 1>() << velocity,
362 spline_->DThetaDt(distance, velocity))
363 .finished();
364 return result;
365}
366
367::std::vector<::Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
368 ::std::chrono::nanoseconds dt) {
369 double dt_float =
370 ::std::chrono::duration_cast<::std::chrono::duration<double>>(dt).count();
371 double t = 0.0;
372 ::Eigen::Matrix<double, 2, 1> state = ::Eigen::Matrix<double, 2, 1>::Zero();
373
374 ::std::vector<::Eigen::Matrix<double, 3, 1>> result;
375 result.emplace_back(FFAcceleration(0));
376 result.back()(1) = 0.0;
377
378 while (state(0) < length() - 1e-4) {
379 t += dt_float;
380
381 // TODO(austin): This feels like something that should be pulled out into
382 // a library for re-use.
383 state = RungeKutta(
384 [this](const ::Eigen::Matrix<double, 2, 1> x) {
385 ::Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
386 return (::Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
387 },
388 state, dt_float);
389
390 result.emplace_back(FFAcceleration(state(0)));
391 state(1) = result.back()(1);
392 }
393 return result;
394}
395
396} // namespace drivetrain
397} // namespace control_loops
398} // namespace frc971