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