blob: 729415cf962dbe77251a8eda032e5c2d0972be59 [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),
James Kuszmaulaa2499d2020-06-02 21:31:19 -070021 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 config_(config),
Austin Schuhec7f06d2019-01-04 07:47:15 +110030 robot_radius_l_(config.robot_radius),
31 robot_radius_r_(config.robot_radius),
James Kuszmaulea314d92019-02-18 19:45:06 -080032 longitudinal_acceleration_(3.0),
Austin Schuhec7f06d2019-01-04 07:47:15 +110033 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_),
James Kuszmaulaa2499d2020-06-02 21:31:19 -070036 1.0 / (robot_radius_l_ + robot_radius_r_))
37 .finished()),
Austin Schuhec7f06d2019-01-04 07:47:15 +110038 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),
James Kuszmaul31ac8912020-02-28 17:12:00 -080043 plan_segment_type_(plan_.size(), 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);
James Kuszmaulea314d92019-02-18 19:45:06 -080048 const double velocity_limit = LateralVelocityCurvature(distance);
49 if (velocity_limit < plan_[i]) {
50 plan_[i] = velocity_limit;
51 plan_segment_type_[i] = CURVATURE_LIMITED;
52 }
Austin Schuhec7f06d2019-01-04 07:47:15 +110053 }
54}
55
James Kuszmaulea314d92019-02-18 19:45:06 -080056void Trajectory::VoltageFeasibilityPass(VoltageLimit limit_type) {
57 for (size_t i = 0; i < plan_.size(); ++i) {
58 const double distance = Distance(i);
59 const double velocity_limit = VoltageVelocityLimit(distance, limit_type);
60 if (velocity_limit < plan_[i]) {
61 plan_[i] = velocity_limit;
62 plan_segment_type_[i] = VOLTAGE_LIMITED;
63 }
64 }
65}
66
67double Trajectory::BestAcceleration(double x, double v, bool backwards) {
Austin Schuhec7f06d2019-01-04 07:47:15 +110068 ::Eigen::Matrix<double, 2, 1> K3;
69 ::Eigen::Matrix<double, 2, 1> K4;
70 ::Eigen::Matrix<double, 2, 1> K5;
71 K345(x, &K3, &K4, &K5);
72
Austin Schuhec7f06d2019-01-04 07:47:15 +110073 // Now, solve for all a's and find the best one which meets our criteria.
James Kuszmaulea314d92019-02-18 19:45:06 -080074 const ::Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
75 double min_voltage_accel = ::std::numeric_limits<double>::infinity();
76 double max_voltage_accel = -min_voltage_accel;
Austin Schuhec7f06d2019-01-04 07:47:15 +110077 for (const double a : {(voltage_limit_ - C(0, 0)) / K5(0, 0),
78 (voltage_limit_ - C(1, 0)) / K5(1, 0),
79 (-voltage_limit_ - C(0, 0)) / K5(0, 0),
80 (-voltage_limit_ - C(1, 0)) / K5(1, 0)}) {
81 const ::Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
82 if ((U.array().abs() < voltage_limit_ + 1e-6).all()) {
James Kuszmaulea314d92019-02-18 19:45:06 -080083 min_voltage_accel = ::std::min(a, min_voltage_accel);
84 max_voltage_accel = ::std::max(a, max_voltage_accel);
Austin Schuhec7f06d2019-01-04 07:47:15 +110085 }
86 }
James Kuszmaulea314d92019-02-18 19:45:06 -080087 double best_accel = backwards ? min_voltage_accel : max_voltage_accel;
Austin Schuhec7f06d2019-01-04 07:47:15 +110088
James Kuszmaulea314d92019-02-18 19:45:06 -080089 double min_friction_accel, max_friction_accel;
90 FrictionLngAccelLimits(x, v, &min_friction_accel, &max_friction_accel);
91 if (backwards) {
92 best_accel = ::std::max(best_accel, min_friction_accel);
93 } else {
94 best_accel = ::std::min(best_accel, max_friction_accel);
Austin Schuhec7f06d2019-01-04 07:47:15 +110095 }
James Kuszmaulea314d92019-02-18 19:45:06 -080096
James Kuszmaul66b78042020-02-23 15:30:51 -080097 // Ideally, the max would never be less than the min, but due to the way that
98 // the runge kutta solver works, it sometimes ticks over the edge.
99 if (max_friction_accel < min_friction_accel) {
100 VLOG(1) << "At x " << x << " v " << v << " min fric acc "
101 << min_friction_accel << " max fric accel " << max_friction_accel;
102 }
103 if (best_accel < min_voltage_accel || best_accel > max_voltage_accel) {
104 LOG(WARNING) << "Viable friction limits and viable voltage limits do not "
105 "overlap (x: " << x << ", v: " << v
106 << ", backwards: " << backwards
107 << ") best_accel = " << best_accel << ", min voltage "
108 << min_voltage_accel << ", max voltage " << max_voltage_accel
109 << " min friction " << min_friction_accel << " max friction "
110 << max_friction_accel << ".";
111
James Kuszmaulea314d92019-02-18 19:45:06 -0800112 // Don't actually do anything--this will just result in attempting to drive
113 // higher voltages thatn we have available. In practice, that'll probably
114 // work out fine.
115 }
116
117 return best_accel;
118}
119
120double Trajectory::LateralVelocityCurvature(double distance) const {
121 // To calculate these constraints, we first note that:
122 // wheel accels = K2 * v_robot' + K1 * v_robot^2
123 // All that this logic does is solve for v_robot, leaving v_robot' free,
124 // assuming that the wheels are at their limits.
125 // To do this, we:
126 //
127 // 1) Determine what the wheel accels will be at the limit--since we have
128 // two free variables (v_robot, v_robot'), both wheels will be at their
129 // limits--if in a sufficiently tight turn (such that the signs of the
130 // coefficients of K2 are different), then the wheels will be accelerating
131 // in opposite directions; otherwise, they accelerate in the same direction.
132 // The magnitude of these per-wheel accelerations is a function of velocity,
133 // so it must also be solved for.
134 //
135 // 2) Eliminate that v_robot' term (since we don't care
136 // about it) by multiplying be a "K2prime" term (where K2prime * K2 = 0) on
137 // both sides of the equation.
138 //
139 // 3) Solving the relatively tractable remaining equation, which is
140 // basically just grouping all the terms together in one spot and taking the
141 // 4th root of everything.
142 const double dtheta = spline_->DTheta(distance);
143 const ::Eigen::Matrix<double, 1, 2> K2prime =
144 K2(dtheta).transpose() *
145 (::Eigen::Matrix<double, 2, 2>() << 0, 1, -1, 0).finished();
146 // Calculate whether the wheels are spinning in opposite directions.
147 const bool opposites = K2prime(0) * K2prime(1) < 0;
148 const ::Eigen::Matrix<double, 2, 1> K1calc = K1(spline_->DDTheta(distance));
149 const double lat_accel_squared =
150 ::std::pow(dtheta / lateral_acceleration_, 2);
151 const double curvature_change_term =
152 (K2prime * K1calc).value() /
153 (K2prime *
154 (::Eigen::Matrix<double, 2, 1>() << 1.0, (opposites ? -1.0 : 1.0))
155 .finished() *
156 longitudinal_acceleration_)
157 .value();
158 const double vel_inv = ::std::sqrt(
159 ::std::sqrt(::std::pow(curvature_change_term, 2) + lat_accel_squared));
160 if (vel_inv == 0.0) {
161 return ::std::numeric_limits<double>::infinity();
162 }
163 return 1.0 / vel_inv;
164}
165
166void Trajectory::FrictionLngAccelLimits(double x, double v, double *min_accel,
167 double *max_accel) const {
168 // First, calculate the max longitudinal acceleration that can be achieved
169 // by either wheel given the friction elliipse that we have.
170 const double lateral_acceleration = v * v * spline_->DDXY(x).norm();
171 const double max_wheel_lng_accel_squared =
172 1.0 - ::std::pow(lateral_acceleration / lateral_acceleration_, 2.0);
173 if (max_wheel_lng_accel_squared < 0.0) {
James Kuszmaul66b78042020-02-23 15:30:51 -0800174 VLOG(1) << "Something (probably Runge-Kutta) queried invalid velocity " << v
175 << " at distance " << x;
James Kuszmaulea314d92019-02-18 19:45:06 -0800176 // If we encounter this, it means that the Runge-Kutta has attempted to
177 // sample points a bit past the edge of the friction boundary. If so, we
178 // gradually ramp the min/max accels to be more and more incorrect (note
179 // how min_accel > max_accel if we reach this case) to avoid causing any
180 // numerical issues.
181 *min_accel =
182 ::std::sqrt(-max_wheel_lng_accel_squared) * longitudinal_acceleration_;
183 *max_accel = -*min_accel;
184 return;
185 }
186 *min_accel = -::std::numeric_limits<double>::infinity();
187 *max_accel = ::std::numeric_limits<double>::infinity();
188
189 // Calculate max/min accelerations by calculating what the robots overall
190 // longitudinal acceleration would be if each wheel were running at the max
191 // forwards/backwards longitudinal acceleration.
192 const double max_wheel_lng_accel =
193 longitudinal_acceleration_ * ::std::sqrt(max_wheel_lng_accel_squared);
194 const ::Eigen::Matrix<double, 2, 1> K1v2 = K1(spline_->DDTheta(x)) * v * v;
195 const ::Eigen::Matrix<double, 2, 1> K2inv =
196 K2(spline_->DTheta(x)).cwiseInverse();
197 // Store the accelerations of the robot corresponding to each wheel being at
198 // the max/min acceleration. The first coefficient in each vector
199 // corresponds to the left wheel, the second to the right wheel.
200 const ::Eigen::Matrix<double, 2, 1> accels1 =
201 K2inv.array() * (-K1v2.array() + max_wheel_lng_accel);
202 const ::Eigen::Matrix<double, 2, 1> accels2 =
203 K2inv.array() * (-K1v2.array() - max_wheel_lng_accel);
204
205 // If either term is non-finite, that suggests that a term of K2 is zero
206 // (which is physically possible when turning such that one wheel is
207 // stationary), so just ignore that side of the drivetrain.
208 if (::std::isfinite(accels1(0))) {
209 // The inner max/min in this case determines which of the two cases (+ or
210 // - acceleration on the left wheel) we care about--in a sufficiently
211 // tight turning radius, the left hweel may be accelerating backwards when
212 // the robot as a whole accelerates forwards. We then use that
213 // acceleration to bound the min/max accel.
214 *min_accel = ::std::max(*min_accel, ::std::min(accels1(0), accels2(0)));
215 *max_accel = ::std::min(*max_accel, ::std::max(accels1(0), accels2(0)));
216 }
217 // Same logic as previous if-statement, but for the right wheel.
218 if (::std::isfinite(accels1(1))) {
219 *min_accel = ::std::max(*min_accel, ::std::min(accels1(1), accels2(1)));
220 *max_accel = ::std::min(*max_accel, ::std::max(accels1(1), accels2(1)));
221 }
222}
223
224double Trajectory::VoltageVelocityLimit(
225 double distance, VoltageLimit limit_type,
226 Eigen::Matrix<double, 2, 1> *constraint_voltages) const {
227 // To sketch an outline of the math going on here, we start with the basic
228 // dynamics of the robot along the spline:
229 // K2 * v_robot' + K1 * v_robot^2 = A * K2 * v_robot + B * U
230 // We need to determine the maximum v_robot given constrained U and free
231 // v_robot'.
232 // Similarly to the friction constraints, we accomplish this by first
233 // multiplying by a K2prime term to eliminate the v_robot' term.
234 // As with the friction constraints, we also know that the limits will occur
235 // when both sides of the drivetrain are driven at their max magnitude
236 // voltages, although they may be driven at different signs.
237 // Once we determine whether the voltages match signs, we still have to
238 // consider both possible pairings (technically we could probably
239 // predetermine which pairing, e.g. +/- or -/+, we acre about, but we don't
240 // need to).
241 //
242 // For each pairing, we then get to solve a quadratic formula for the robot
243 // velocity at those voltages. This gives us up to 4 solutions, of which
244 // up to 3 will give us positive velocities; each solution velocity
245 // corresponds to a transition from feasibility to infeasibility, where a
246 // velocity of zero is always feasible, and there will always be 0, 1, or 3
247 // positive solutions. Among the positive solutions, we take both the min
248 // and the max--the min will be the highest velocity such that all
249 // velocities between zero and that velocity are valid; the max will be
250 // the highest feasible velocity. Which we return depends on what the
251 // limit_type is.
252 //
253 // Sketching the actual math:
254 // K2 * v_robot' + K1 * v_robot^2 = A * K2 * v_robot +/- B * U_max
255 // K2prime * K1 * v_robot^2 = K2prime * (A * K2 * v_robot +/- B * U_max)
256 // a v_robot^2 + b v_robot +/- c = 0
257 const ::Eigen::Matrix<double, 2, 2> B =
258 velocity_drivetrain_->plant().coefficients().B_continuous;
259 const double dtheta = spline_->DTheta(distance);
260 const ::Eigen::Matrix<double, 2, 1> BinvK2 = B.inverse() * K2(dtheta);
261 // Because voltages can actually impact *both* wheels, in order to determine
262 // whether the voltages will have opposite signs, we need to use B^-1 * K2.
263 const bool opposite_voltages = BinvK2(0) * BinvK2(1) > 0.0;
264 const ::Eigen::Matrix<double, 1, 2> K2prime =
265 K2(dtheta).transpose() *
266 (::Eigen::Matrix<double, 2, 2>() << 0, 1, -1, 0).finished();
267 const double a = K2prime * K1(spline_->DDTheta(distance));
268 const double b = -K2prime *
269 velocity_drivetrain_->plant().coefficients().A_continuous *
270 K2(dtheta);
271 const ::Eigen::Matrix<double, 1, 2> c_coeff = -K2prime * B;
272 // Calculate the "positive" version of the voltage limits we will use.
273 const ::Eigen::Matrix<double, 2, 1> abs_volts =
274 voltage_limit_ *
275 (::Eigen::Matrix<double, 2, 1>() << 1.0, (opposite_voltages ? -1.0 : 1.0))
276 .finished();
277
278 double min_valid_vel = ::std::numeric_limits<double>::infinity();
279 if (limit_type == VoltageLimit::kAggressive) {
280 min_valid_vel = 0.0;
281 }
282 // Iterate over both possibilites for +/- voltage, and solve the quadratic
283 // formula. For every positive solution, adjust the velocity limit
284 // appropriately.
285 for (const double sign : {1.0, -1.0}) {
286 const ::Eigen::Matrix<double, 2, 1> U = sign * abs_volts;
287 const double prev_vel = min_valid_vel;
288 const double c = c_coeff * U;
289 const double determinant = b * b - 4 * a * c;
290 if (a == 0) {
291 // If a == 0, that implies we are on a constant curvature path, in which
292 // case we just have b * v + c = 0.
293 // Note that if -b * c > 0.0, then vel will be greater than zero and b
294 // will be non-zero.
295 if (-b * c > 0.0) {
296 const double vel = -c / b;
297 if (limit_type == VoltageLimit::kConservative) {
298 min_valid_vel = ::std::min(min_valid_vel, vel);
299 } else {
300 min_valid_vel = ::std::max(min_valid_vel, vel);
301 }
302 } else if (b == 0) {
303 // If a and b are zero, then we are travelling in a straight line and
304 // have no voltage-based velocity constraints.
305 min_valid_vel = ::std::numeric_limits<double>::infinity();
306 }
307 } else if (determinant > 0) {
308 const double sqrt_determinant = ::std::sqrt(determinant);
309 const double high_vel = (-b + sqrt_determinant) / (2.0 * a);
310 const double low_vel = (-b - sqrt_determinant) / (2.0 * a);
311 if (low_vel > 0) {
312 if (limit_type == VoltageLimit::kConservative) {
313 min_valid_vel = ::std::min(min_valid_vel, low_vel);
314 } else {
315 min_valid_vel = ::std::max(min_valid_vel, low_vel);
316 }
317 }
318 if (high_vel > 0) {
319 if (limit_type == VoltageLimit::kConservative) {
320 min_valid_vel = ::std::min(min_valid_vel, high_vel);
321 } else {
322 min_valid_vel = ::std::max(min_valid_vel, high_vel);
323 }
324 }
325 } else if (determinant == 0 && -b * a > 0) {
326 const double vel = -b / (2.0 * a);
327 if (vel > 0.0) {
328 if (limit_type == VoltageLimit::kConservative) {
329 min_valid_vel = ::std::min(min_valid_vel, vel);
330 } else {
331 min_valid_vel = ::std::max(min_valid_vel, vel);
332 }
333 }
334 }
335 if (constraint_voltages != nullptr && prev_vel != min_valid_vel) {
336 *constraint_voltages = U;
337 }
338 }
339 return min_valid_vel;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100340}
341
342void Trajectory::ForwardPass() {
343 plan_[0] = 0.0;
344 const double delta_distance = Distance(1) - Distance(0);
345 for (size_t i = 0; i < plan_.size() - 1; ++i) {
346 const double distance = Distance(i);
347
348 // Integrate our acceleration forward one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800349 const double new_plan_velocity = IntegrateAccelForDistance(
350 [this](double x, double v) { return ForwardAcceleration(x, v); },
351 plan_[i], distance, delta_distance);
352
James Kuszmaulea314d92019-02-18 19:45:06 -0800353 if (new_plan_velocity <= plan_[i + 1]) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800354 plan_[i + 1] = new_plan_velocity;
355 plan_segment_type_[i] = SegmentType::ACCELERATION_LIMITED;
356 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100357 }
358}
359
Austin Schuhec7f06d2019-01-04 07:47:15 +1100360void Trajectory::BackwardPass() {
361 const double delta_distance = Distance(0) - Distance(1);
362 plan_.back() = 0.0;
363 for (size_t i = plan_.size() - 1; i > 0; --i) {
364 const double distance = Distance(i);
365
366 // Integrate our deceleration back one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800367 const double new_plan_velocity = IntegrateAccelForDistance(
368 [this](double x, double v) { return BackwardAcceleration(x, v); },
369 plan_[i], distance, delta_distance);
370
James Kuszmaulea314d92019-02-18 19:45:06 -0800371 if (new_plan_velocity <= plan_[i - 1]) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800372 plan_[i - 1] = new_plan_velocity;
373 plan_segment_type_[i - 1] = SegmentType::DECELERATION_LIMITED;
374 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100375 }
376}
377
378::Eigen::Matrix<double, 3, 1> Trajectory::FFAcceleration(double distance) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800379 if (distance < 0.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100380 // Make sure we don't end up off the beginning of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800381 distance = 0.0;
382 } else if (distance > length()) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100383 // Make sure we don't end up off the end of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800384 distance = length();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100385 }
Austin Schuhe73a9052019-01-07 12:16:17 -0800386 const size_t before_index = DistanceToSegment(distance);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700387 const size_t after_index = std::min(before_index + 1, plan_.size() - 1);
Austin Schuhe73a9052019-01-07 12:16:17 -0800388
Austin Schuhec7f06d2019-01-04 07:47:15 +1100389 const double before_distance = Distance(before_index);
390 const double after_distance = Distance(after_index);
391
Austin Schuhec7f06d2019-01-04 07:47:15 +1100392 // And then also make sure we aren't curvature limited.
393 const double vcurvature = LateralVelocityCurvature(distance);
394
395 double acceleration;
396 double velocity;
James Kuszmaulea314d92019-02-18 19:45:06 -0800397 // TODO(james): While technically correct for sufficiently small segment
398 // steps, this method of switching between limits has a tendency to produce
399 // sudden jumps in acceelrations, which is undesirable.
Austin Schuhe73a9052019-01-07 12:16:17 -0800400 switch (plan_segment_type_[DistanceToSegment(distance)]) {
401 case SegmentType::VELOCITY_LIMITED:
402 acceleration = 0.0;
403 velocity = (plan_[before_index] + plan_[after_index]) / 2.0;
404 // TODO(austin): Accelerate or decelerate until we hit the limit in the
405 // time slice. Otherwise our acceleration will be lying for this slice.
406 // Do note, we've got small slices so the effect will be small.
407 break;
408 case SegmentType::CURVATURE_LIMITED:
409 velocity = vcurvature;
James Kuszmaulea314d92019-02-18 19:45:06 -0800410 FrictionLngAccelLimits(distance, velocity, &acceleration, &acceleration);
411 break;
412 case SegmentType::VOLTAGE_LIMITED:
413 // Normally, we expect that voltage limited plans will all get dominated
414 // by the acceleration/deceleration limits. This may not always be true;
415 // if we ever encounter this error, we just need to back out what the
416 // accelerations would be in this case.
417 LOG(FATAL) << "Unexpectedly got VOLTAGE_LIMITED plan.";
Austin Schuhe73a9052019-01-07 12:16:17 -0800418 break;
419 case SegmentType::ACCELERATION_LIMITED:
James Kuszmaulea314d92019-02-18 19:45:06 -0800420 // TODO(james): The integration done here and in the DECELERATION_LIMITED
421 // can technically cause us to violate friction constraints. We currently
422 // don't do anything about it to avoid causing sudden jumps in voltage,
423 // but we probably *should* at some point.
Austin Schuhe73a9052019-01-07 12:16:17 -0800424 velocity = IntegrateAccelForDistance(
425 [this](double x, double v) { return ForwardAcceleration(x, v); },
426 plan_[before_index], before_distance, distance - before_distance);
427 acceleration = ForwardAcceleration(distance, velocity);
428 break;
429 case SegmentType::DECELERATION_LIMITED:
430 velocity = IntegrateAccelForDistance(
431 [this](double x, double v) { return BackwardAcceleration(x, v); },
432 plan_[after_index], after_distance, distance - after_distance);
433 acceleration = BackwardAcceleration(distance, velocity);
434 break;
435 default:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700436 AOS_LOG(
437 FATAL, "Unknown segment type %d\n",
Austin Schuhe73a9052019-01-07 12:16:17 -0800438 static_cast<int>(plan_segment_type_[DistanceToSegment(distance)]));
439 break;
440 }
441
Austin Schuhec7f06d2019-01-04 07:47:15 +1100442 return (::Eigen::Matrix<double, 3, 1>() << distance, velocity, acceleration)
443 .finished();
444}
445
446::Eigen::Matrix<double, 2, 1> Trajectory::FFVoltage(double distance) {
447 const Eigen::Matrix<double, 3, 1> xva = FFAcceleration(distance);
448 const double velocity = xva(1);
449 const double acceleration = xva(2);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100450
Austin Schuhe73a9052019-01-07 12:16:17 -0800451 ::Eigen::Matrix<double, 2, 1> K3;
452 ::Eigen::Matrix<double, 2, 1> K4;
453 ::Eigen::Matrix<double, 2, 1> K5;
454 K345(distance, &K3, &K4, &K5);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100455
456 return K5 * acceleration + K3 * velocity * velocity + K4 * velocity;
457}
458
459const ::std::vector<double> Trajectory::Distances() const {
460 ::std::vector<double> d;
461 d.reserve(plan_.size());
462 for (size_t i = 0; i < plan_.size(); ++i) {
463 d.push_back(Distance(i));
464 }
465 return d;
466}
467
468::Eigen::Matrix<double, 5, 5> Trajectory::ALinearizedContinuous(
469 const ::Eigen::Matrix<double, 5, 1> &state) const {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100470 const double sintheta = ::std::sin(state(2));
471 const double costheta = ::std::cos(state(2));
472 const ::Eigen::Matrix<double, 2, 1> linear_angular =
473 Tlr_to_la_ * state.block<2, 1>(3, 0);
474
475 // When stopped, just roll with a min velocity.
476 double linear_velocity = 0.0;
477 constexpr double kMinVelocity = 0.1;
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700478 if (::std::abs(linear_angular(0)) < kMinVelocity / 100.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100479 linear_velocity = 0.1;
480 } else if (::std::abs(linear_angular(0)) > kMinVelocity) {
481 linear_velocity = linear_angular(0);
482 } else if (linear_angular(0) > 0) {
483 linear_velocity = kMinVelocity;
484 } else if (linear_angular(0) < 0) {
485 linear_velocity = -kMinVelocity;
486 }
487
488 ::Eigen::Matrix<double, 5, 5> result = ::Eigen::Matrix<double, 5, 5>::Zero();
489 result(0, 2) = -sintheta * linear_velocity;
490 result(0, 3) = 0.5 * costheta;
491 result(0, 4) = 0.5 * costheta;
492
493 result(1, 2) = costheta * linear_velocity;
494 result(1, 3) = 0.5 * sintheta;
495 result(1, 4) = 0.5 * sintheta;
496
497 result(2, 3) = Tlr_to_la_(1, 0);
498 result(2, 4) = Tlr_to_la_(1, 1);
499
500 result.block<2, 2>(3, 3) =
501 velocity_drivetrain_->plant().coefficients().A_continuous;
502 return result;
503}
504
505::Eigen::Matrix<double, 5, 2> Trajectory::BLinearizedContinuous() const {
506 ::Eigen::Matrix<double, 5, 2> result = ::Eigen::Matrix<double, 5, 2>::Zero();
507 result.block<2, 2>(3, 0) =
508 velocity_drivetrain_->plant().coefficients().B_continuous;
509 return result;
510}
511
512void Trajectory::AB(const ::Eigen::Matrix<double, 5, 1> &state,
513 ::std::chrono::nanoseconds dt,
514 ::Eigen::Matrix<double, 5, 5> *A,
515 ::Eigen::Matrix<double, 5, 2> *B) const {
516 ::Eigen::Matrix<double, 5, 5> A_linearized_continuous =
517 ALinearizedContinuous(state);
518 ::Eigen::Matrix<double, 5, 2> B_linearized_continuous =
519 BLinearizedContinuous();
520
521 // Now, convert it to discrete.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700522 controls::C2D(A_linearized_continuous, B_linearized_continuous, dt, A, B);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100523}
524
525::Eigen::Matrix<double, 2, 5> Trajectory::KForState(
526 const ::Eigen::Matrix<double, 5, 1> &state, ::std::chrono::nanoseconds dt,
527 const ::Eigen::DiagonalMatrix<double, 5> &Q,
528 const ::Eigen::DiagonalMatrix<double, 2> &R) const {
529 ::Eigen::Matrix<double, 5, 5> A;
530 ::Eigen::Matrix<double, 5, 2> B;
531 AB(state, dt, &A, &B);
532
533 ::Eigen::Matrix<double, 5, 5> S = ::Eigen::Matrix<double, 5, 5>::Zero();
534 ::Eigen::Matrix<double, 2, 5> K = ::Eigen::Matrix<double, 2, 5>::Zero();
535
536 int info = ::frc971::controls::dlqr<5, 2>(A, B, Q, R, &K, &S);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700537 if (info != 0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700538 AOS_LOG(ERROR, "Failed to solve %d, controllability: %d\n", info,
539 controls::Controllability(A, B));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100540 // TODO(austin): Can we be more clever here? Use the last one? We should
541 // collect more info about when this breaks down from logs.
542 K = ::Eigen::Matrix<double, 2, 5>::Zero();
543 }
James Kuszmaul1bce5772020-03-08 22:13:50 -0700544 if (VLOG_IS_ON(1)) {
545 ::Eigen::EigenSolver<::Eigen::Matrix<double, 5, 5>> eigensolver(A - B * K);
546 const auto eigenvalues = eigensolver.eigenvalues();
547 LOG(INFO) << "Eigenvalues: " << eigenvalues;
548 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100549 return K;
550}
551
552const ::Eigen::Matrix<double, 5, 1> Trajectory::GoalState(double distance,
553 double velocity) {
554 ::Eigen::Matrix<double, 5, 1> result;
555 result.block<2, 1>(0, 0) = spline_->XY(distance);
556 result(2, 0) = spline_->Theta(distance);
557
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700558 result.block<2, 1>(3, 0) =
559 Tla_to_lr_ * (::Eigen::Matrix<double, 2, 1>() << velocity,
560 spline_->DThetaDt(distance, velocity))
561 .finished();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100562 return result;
563}
564
Alex Perry4ae2fd72019-02-03 15:55:57 -0800565::Eigen::Matrix<double, 3, 1> Trajectory::GetNextXVA(
566 ::std::chrono::nanoseconds dt, ::Eigen::Matrix<double, 2, 1> *state) {
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700567 double dt_float = ::aos::time::DurationInSeconds(dt);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100568
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800569 const double last_distance = (*state)(0);
Alex Perry4ae2fd72019-02-03 15:55:57 -0800570 // TODO(austin): This feels like something that should be pulled out into
571 // a library for re-use.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700572 *state = RungeKutta(
573 [this](const ::Eigen::Matrix<double, 2, 1> x) {
574 ::Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
575 return (::Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
576 },
577 *state, dt_float);
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800578 // Force the distance to move forwards, to guarantee that we actually finish
579 // the planning.
580 constexpr double kMinDistanceIncrease = 1e-7;
581 if ((*state)(0) < last_distance + kMinDistanceIncrease) {
582 (*state)(0) = last_distance + kMinDistanceIncrease;
583 }
Alex Perry4ae2fd72019-02-03 15:55:57 -0800584
585 ::Eigen::Matrix<double, 3, 1> result = FFAcceleration((*state)(0));
586 (*state)(1) = result(1);
587 return result;
588}
589
590::std::vector<::Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
591 ::std::chrono::nanoseconds dt) {
592 ::Eigen::Matrix<double, 2, 1> state = ::Eigen::Matrix<double, 2, 1>::Zero();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100593 ::std::vector<::Eigen::Matrix<double, 3, 1>> result;
594 result.emplace_back(FFAcceleration(0));
595 result.back()(1) = 0.0;
596
Alex Perry4ae2fd72019-02-03 15:55:57 -0800597 while (!is_at_end(state)) {
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800598 if (state_is_faulted(state)) {
599 LOG(WARNING)
600 << "Found invalid state in generating spline and aborting. This is "
601 "likely due to a spline with extremely high jerk/changes in "
602 "curvature with an insufficiently small step size.";
603 return {};
604 }
Alex Perry4ae2fd72019-02-03 15:55:57 -0800605 result.emplace_back(GetNextXVA(dt, &state));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100606 }
607 return result;
608}
609
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800610void Trajectory::LimitVelocity(double starting_distance, double ending_distance,
611 const double max_velocity) {
612 const double segment_length = ending_distance - starting_distance;
613
614 const double min_length = length() / static_cast<double>(plan_.size() - 1);
615 if (starting_distance > ending_distance) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700616 AOS_LOG(FATAL, "End before start: %f > %f\n", starting_distance,
617 ending_distance);
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800618 }
619 starting_distance = ::std::min(length(), ::std::max(0.0, starting_distance));
620 ending_distance = ::std::min(length(), ::std::max(0.0, ending_distance));
621 if (segment_length < min_length) {
622 const size_t plan_index = static_cast<size_t>(
623 ::std::round((starting_distance + ending_distance) / 2.0 / min_length));
624 if (max_velocity < plan_[plan_index]) {
625 plan_[plan_index] = max_velocity;
626 }
627 } else {
628 for (size_t i = DistanceToSegment(starting_distance) + 1;
629 i < DistanceToSegment(ending_distance) + 1; ++i) {
630 if (max_velocity < plan_[i]) {
631 plan_[i] = max_velocity;
632 if (i < DistanceToSegment(ending_distance)) {
633 plan_segment_type_[i] = SegmentType::VELOCITY_LIMITED;
634 }
635 }
636 }
637 }
638}
639
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700640void Trajectory::PathRelativeContinuousSystem(double distance,
641 Eigen::Matrix<double, 5, 5> *A,
642 Eigen::Matrix<double, 5, 2> *B) {
643 const double nominal_velocity = FFAcceleration(distance)(1);
644 const double dtheta_dt = spline_->DThetaDt(distance, nominal_velocity);
645 // Calculate the "path-relative" coordinates, which are:
646 // [[distance along the path],
647 // [lateral position along path],
648 // [theta],
649 // [left wheel velocity],
650 // [right wheel velocity]]
651 Eigen::Matrix<double, 5, 1> nominal_X;
652 nominal_X << distance, 0.0, 0.0,
653 nominal_velocity - dtheta_dt * robot_radius_l_,
654 nominal_velocity + dtheta_dt * robot_radius_r_;
655 PathRelativeContinuousSystem(nominal_X, A, B);
656}
657
658void Trajectory::PathRelativeContinuousSystem(
659 const Eigen::Matrix<double, 5, 1> &X, Eigen::Matrix<double, 5, 5> *A,
660 Eigen::Matrix<double, 5, 2> *B) {
661 A->setZero();
662 B->setZero();
663 const double theta = X(2);
664 const double ctheta = std::cos(theta);
665 const double stheta = std::sin(theta);
666 const double curvature = spline_->DTheta(X(0));
667 const double longitudinal_velocity = (X(3) + X(4)) / 2.0;
668 const double diameter = robot_radius_l_ + robot_radius_r_;
669 // d_dpath / dt = (v_left + v_right) / 2.0 * cos(theta)
670 // (d_dpath / dt) / dv_left = cos(theta) / 2.0
671 (*A)(0, 3) = ctheta / 2.0;
672 // (d_dpath / dt) / dv_right = cos(theta) / 2.0
673 (*A)(0, 4) = ctheta / 2.0;
674 // (d_dpath / dt) / dtheta = -(v_left + v_right) / 2.0 * sin(theta)
675 (*A)(0, 2) = -longitudinal_velocity * stheta;
676 // d_dlat / dt = (v_left + v_right) / 2.0 * sin(theta)
677 // (d_dlat / dt) / dv_left = sin(theta) / 2.0
678 (*A)(1, 3) = stheta / 2.0;
679 // (d_dlat / dt) / dv_right = sin(theta) / 2.0
680 (*A)(1, 4) = stheta / 2.0;
681 // (d_dlat / dt) / dtheta = (v_left + v_right) / 2.0 * cos(theta)
682 (*A)(1, 2) = longitudinal_velocity * ctheta;
683 // dtheta / dt = (v_right - v_left) / diameter - curvature * (v_left +
684 // v_right) / 2.0
685 // (dtheta / dt) / dv_left = -1.0 / diameter - curvature / 2.0
686 (*A)(2, 3) = -1.0 / diameter - curvature / 2.0;
687 // (dtheta / dt) / dv_right = 1.0 / diameter - curvature / 2.0
688 (*A)(2, 4) = 1.0 / diameter - curvature / 2.0;
689 // v_{left,right} / dt = the normal LTI system.
690 A->block<2, 2>(3, 3) =
691 velocity_drivetrain_->plant().coefficients().A_continuous;
692 B->block<2, 2>(3, 0) =
693 velocity_drivetrain_->plant().coefficients().B_continuous;
694}
695
696double Trajectory::EstimateDistanceAlongPath(
697 double nominal_distance, const Eigen::Matrix<double, 5, 1> &state) {
698 const double nominal_theta = spline_->Theta(nominal_distance);
699 const Eigen::Matrix<double, 2, 1> xy_err =
700 state.block<2, 1>(0, 0) - spline_->XY(nominal_distance);
701 return nominal_distance + xy_err.x() * std::cos(nominal_theta) +
702 xy_err.y() * std::sin(nominal_theta);
703}
704
705Eigen::Matrix<double, 5, 1> Trajectory::StateToPathRelativeState(
706 double distance, const Eigen::Matrix<double, 5, 1> &state) {
707 const double nominal_theta = spline_->Theta(distance);
708 const Eigen::Matrix<double, 2, 1> nominal_xy = spline_->XY(distance);
709 const Eigen::Matrix<double, 2, 1> xy_err =
710 state.block<2, 1>(0, 0) - nominal_xy;
711 const double ctheta = std::cos(nominal_theta);
712 const double stheta = std::sin(nominal_theta);
713 Eigen::Matrix<double, 5, 1> path_state;
714 path_state(0) = distance + xy_err.x() * ctheta + xy_err.y() * stheta;
715 path_state(1) = -xy_err.x() * stheta + xy_err.y() * ctheta;
716 path_state(2) = state(2) - nominal_theta;
717 path_state(3) = state(3);
718 path_state(4) = state(4);
719 return path_state;
720}
721
722// Path-relative controller method:
723// For the path relative controller, we use a non-standard version of LQR to
724// perform the control. Essentially, we first transform the system into
725// a set of path-relative coordinates (where the reference that we use is the
726// desired path reference). This gives us a system that is linear and
727// time-varying, i.e. the system is a set of A_k, B_k matrices for each
728// timestep k.
729// In order to control this, we use a discrete-time finite-horizon LQR, using
730// the appropraite [AB]_k for the given timestep. Note that the finite-horizon
731// LQR requires choosing a terminal cost (i.e., what the cost should be
732// for if we have not precisely reached the goal at the end of the time-period).
733// For this, I approximate the infinite-horizon LQR solution by extending the
734// finite-horizon much longer (albeit with the extension just using the
735// linearization for the infal point).
736void Trajectory::CalculatePathGains() {
737 const std::vector<Eigen::Matrix<double, 3, 1>> xva_plan = PlanXVA(config_.dt);
738 plan_gains_.resize(xva_plan.size());
739
740 // Set up reasonable gain matrices. Current choices of gains are arbitrary
741 // and just setup to work well enough for the simulation tests.
742 // TODO(james): Tune this on a real robot.
743 // TODO(james): Pull these out into a config.
744 Eigen::Matrix<double, 5, 5> Q;
745 Q.setIdentity();
746 Q.diagonal() << 20.0, 20.0, 20.0, 10.0, 10.0;
747 Q *= 2.0;
748 Q = (Q * Q).eval();
749
750 Eigen::Matrix<double, 2, 2> R;
751 R.setIdentity();
752 R *= 5.0;
753
754 Eigen::Matrix<double, 5, 5> P = Q;
755
756 CHECK_LT(0u, xva_plan.size());
757 const int max_index = static_cast<int>(xva_plan.size()) - 1;
758 for (int i = max_index; i >= 0; --i) {
759 const double distance = xva_plan[i](0);
760 Eigen::Matrix<double, 5, 5> A_continuous;
761 Eigen::Matrix<double, 5, 2> B_continuous;
762 PathRelativeContinuousSystem(distance, &A_continuous, &B_continuous);
763 Eigen::Matrix<double, 5, 5> A_discrete;
764 Eigen::Matrix<double, 5, 2> B_discrete;
765 controls::C2D(A_continuous, B_continuous, config_.dt, &A_discrete,
766 &B_discrete);
767
768 if (i == max_index) {
769 // At the final timestep, approximate P by iterating a bunch of times.
770 // This is terminal cost mentioned in function-level comments.
771 // This does a very loose job of solving the DARE. Ideally, we would
772 // actually use a DARE solver directly, but based on some initial testing,
773 // this method is a bit more robust (or, at least, it is a bit more robust
774 // if we don't want to spend more time handling the potential error
775 // cases the DARE solver can encounter).
776 constexpr int kExtraIters = 100;
777 for (int jj = 0; jj < kExtraIters; ++jj) {
778 const Eigen::Matrix<double, 5, 5> AP = A_discrete.transpose() * P;
779 const Eigen::Matrix<double, 5, 2> APB = AP * B_discrete;
780 const Eigen::Matrix<double, 2, 2> RBPBinv =
781 (R + B_discrete.transpose() * P * B_discrete).inverse();
782 P = AP * A_discrete - APB * RBPBinv * APB.transpose() + Q;
783 }
784 }
785
786 const Eigen::Matrix<double, 5, 5> AP = A_discrete.transpose() * P;
787 const Eigen::Matrix<double, 5, 2> APB = AP * B_discrete;
788 const Eigen::Matrix<double, 2, 2> RBPBinv =
789 (R + B_discrete.transpose() * P * B_discrete).inverse();
790 plan_gains_[i].first = distance;
791 plan_gains_[i].second = RBPBinv * APB.transpose();
792 P = AP * A_discrete - APB * plan_gains_[i].second + Q;
793 }
794}
795
796Eigen::Matrix<double, 2, 5> Trajectory::GainForDistance(double distance) {
797 CHECK(!plan_gains_.empty());
798 size_t index = 0;
799 for (index = 0; index < plan_gains_.size() - 1; ++index) {
800 if (plan_gains_[index + 1].first > distance) {
801 break;
802 }
803 }
804 return plan_gains_[index].second;
805}
806
Austin Schuhec7f06d2019-01-04 07:47:15 +1100807} // namespace drivetrain
808} // namespace control_loops
809} // namespace frc971