blob: 4db41ee031813547c0cf0a8ea6cf9c3b94db8ab1 [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"
Philipp Schrader790cb542023-07-05 21:06:52 -07006
7#include "aos/util/math.h"
Austin Schuhec7f06d2019-01-04 07:47:15 +11008#include "frc971/control_loops/c2d.h"
James Kuszmaul651fc3f2019-05-15 21:14:25 -07009#include "frc971/control_loops/dlqr.h"
Austin Schuhec7f06d2019-01-04 07:47:15 +110010#include "frc971/control_loops/drivetrain/distance_spline.h"
11#include "frc971/control_loops/drivetrain/drivetrain_config.h"
12#include "frc971/control_loops/hybrid_state_feedback_loop.h"
13#include "frc971/control_loops/state_feedback_loop.h"
14
Stephan Pleinesf63bde82024-01-13 15:59:33 -080015namespace frc971::control_loops::drivetrain {
Austin Schuhec7f06d2019-01-04 07:47:15 +110016
James Kuszmaul75a18c52021-03-10 22:02:07 -080017namespace {
18float DefaultConstraint(ConstraintType type) {
19 switch (type) {
20 case ConstraintType::LONGITUDINAL_ACCELERATION:
21 return 2.0;
22 case ConstraintType::LATERAL_ACCELERATION:
23 return 3.0;
24 case ConstraintType::VOLTAGE:
25 return 12.0;
26 case ConstraintType::VELOCITY:
27 case ConstraintType::CONSTRAINT_TYPE_UNDEFINED:
28 LOG(FATAL) << "No default constraint value for "
29 << EnumNameConstraintType(type);
30 }
31 LOG(FATAL) << "Invalid ConstraintType " << static_cast<int>(type);
32}
33} // namespace
34
Austin Schuhf7c65202022-11-04 21:28:20 -070035FinishedTrajectory::FinishedTrajectory(
James Kuszmaul5c4ccf62024-03-03 17:29:49 -080036 const DrivetrainConfig<double> *config, const fb::Trajectory *buffer,
Austin Schuhf7c65202022-11-04 21:28:20 -070037 std::shared_ptr<
38 StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
39 HybridKalman<2, 2, 2>>>
40 velocity_drivetrain)
Austin Schuh6bdcc372024-06-27 14:49:11 -070041 : BaseTrajectory(
42 [&]() {
43 CHECK(buffer->spline() != nullptr);
44 CHECK(buffer->spline()->spline() != nullptr);
45 return buffer->spline()->spline()->constraints();
46 }(),
47 config, std::move(velocity_drivetrain)),
James Kuszmaul75a18c52021-03-10 22:02:07 -080048 buffer_(buffer),
49 spline_(*buffer_->spline()) {}
50
51const Eigen::Matrix<double, 2, 1> BaseTrajectory::K1(
52 double current_ddtheta) const {
53 return (Eigen::Matrix<double, 2, 1>() << -robot_radius_l_ * current_ddtheta,
54 robot_radius_r_ * current_ddtheta)
55 .finished();
56}
57
58const Eigen::Matrix<double, 2, 1> BaseTrajectory::K2(
59 double current_dtheta) const {
60 return (Eigen::Matrix<double, 2, 1>()
61 << 1.0 - robot_radius_l_ * current_dtheta,
62 1.0 + robot_radius_r_ * current_dtheta)
63 .finished();
64}
65
66void BaseTrajectory::K345(const double x, Eigen::Matrix<double, 2, 1> *K3,
67 Eigen::Matrix<double, 2, 1> *K4,
68 Eigen::Matrix<double, 2, 1> *K5) const {
69 const double current_ddtheta = spline().DDTheta(x);
70 const double current_dtheta = spline().DTheta(x);
71 // We've now got the equation:
72 // K2 * d^x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
73 const Eigen::Matrix<double, 2, 1> my_K2 = K2(current_dtheta);
74
75 const Eigen::Matrix<double, 2, 2> B_inverse =
76 velocity_drivetrain_->plant().coefficients().B_continuous.inverse();
77
78 // Now, rephrase it as K5 a + K3 v^2 + K4 v = U
79 *K3 = B_inverse * K1(current_ddtheta);
80 *K4 = -B_inverse * velocity_drivetrain_->plant().coefficients().A_continuous *
81 my_K2;
82 *K5 = B_inverse * my_K2;
83}
84
85BaseTrajectory::BaseTrajectory(
86 const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
James Kuszmaul5c4ccf62024-03-03 17:29:49 -080087 const DrivetrainConfig<double> *config,
Austin Schuhf7c65202022-11-04 21:28:20 -070088 std::shared_ptr<
89 StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
90 HybridKalman<2, 2, 2>>>
91 velocity_drivetrain)
92 : velocity_drivetrain_(std::move(velocity_drivetrain)),
James Kuszmaulaa2499d2020-06-02 21:31:19 -070093 config_(config),
James Kuszmaul5c4ccf62024-03-03 17:29:49 -080094 robot_radius_l_(config->robot_radius),
95 robot_radius_r_(config->robot_radius),
James Kuszmaul75a18c52021-03-10 22:02:07 -080096 lateral_acceleration_(
97 ConstraintValue(constraints, ConstraintType::LATERAL_ACCELERATION)),
98 longitudinal_acceleration_(ConstraintValue(
99 constraints, ConstraintType::LONGITUDINAL_ACCELERATION)),
100 voltage_limit_(ConstraintValue(constraints, ConstraintType::VOLTAGE)) {}
101
102Trajectory::Trajectory(const SplineGoal &spline_goal,
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800103 const DrivetrainConfig<double> *config)
James Kuszmaul75a18c52021-03-10 22:02:07 -0800104 : Trajectory(DistanceSpline{spline_goal.spline()}, config,
105 spline_goal.spline()->constraints(),
106 spline_goal.spline_idx()) {
107 drive_spline_backwards_ = spline_goal.drive_spline_backwards();
108}
109
110Trajectory::Trajectory(
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800111 DistanceSpline &&input_spline, const DrivetrainConfig<double> *config,
James Kuszmaul75a18c52021-03-10 22:02:07 -0800112 const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
113 int spline_idx, double vmax, int num_distance)
114 : BaseTrajectory(constraints, config),
115 spline_idx_(spline_idx),
116 spline_(std::move(input_spline)),
117 config_(config),
Austin Schuhe73a9052019-01-07 12:16:17 -0800118 plan_(num_distance == 0
Austin Schuh890196c2021-03-31 20:18:45 -0700119 ? std::max(10000, static_cast<int>(spline_.length() / 0.0025))
Austin Schuhe73a9052019-01-07 12:16:17 -0800120 : num_distance,
121 vmax),
James Kuszmaul75a18c52021-03-10 22:02:07 -0800122 plan_segment_type_(plan_.size(),
123 fb::SegmentConstraint::VELOCITY_LIMITED) {
124 if (constraints != nullptr) {
125 for (const Constraint *constraint : *constraints) {
126 if (constraint->constraint_type() == ConstraintType::VELOCITY) {
127 LimitVelocity(constraint->start_distance(), constraint->end_distance(),
128 constraint->value());
129 }
130 }
131 }
132}
Austin Schuhec7f06d2019-01-04 07:47:15 +1100133
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800134Trajectory::Trajectory(
135 DistanceSpline &&spline, std::unique_ptr<DrivetrainConfig<double>> config,
136 const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
137 int spline_idx, double vmax, int num_distance)
138 : Trajectory(std::move(spline), config.get(), constraints, spline_idx, vmax,
139 num_distance) {
140 owned_config_ = std::move(config);
141}
142
Austin Schuhec7f06d2019-01-04 07:47:15 +1100143void Trajectory::LateralAccelPass() {
144 for (size_t i = 0; i < plan_.size(); ++i) {
145 const double distance = Distance(i);
Austin Schuhd749d932020-12-30 21:38:40 -0800146 const double velocity_limit = LateralVelocityCurvature(distance);
James Kuszmaulea314d92019-02-18 19:45:06 -0800147 if (velocity_limit < plan_[i]) {
148 plan_[i] = velocity_limit;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800149 plan_segment_type_[i] = fb::SegmentConstraint::CURVATURE_LIMITED;
James Kuszmaulea314d92019-02-18 19:45:06 -0800150 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100151 }
152}
153
James Kuszmaulea314d92019-02-18 19:45:06 -0800154void Trajectory::VoltageFeasibilityPass(VoltageLimit limit_type) {
155 for (size_t i = 0; i < plan_.size(); ++i) {
156 const double distance = Distance(i);
157 const double velocity_limit = VoltageVelocityLimit(distance, limit_type);
158 if (velocity_limit < plan_[i]) {
159 plan_[i] = velocity_limit;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800160 plan_segment_type_[i] = fb::SegmentConstraint::VOLTAGE_LIMITED;
James Kuszmaulea314d92019-02-18 19:45:06 -0800161 }
162 }
163}
164
James Kuszmaul75a18c52021-03-10 22:02:07 -0800165double BaseTrajectory::BestAcceleration(double x, double v,
166 bool backwards) const {
167 Eigen::Matrix<double, 2, 1> K3;
168 Eigen::Matrix<double, 2, 1> K4;
169 Eigen::Matrix<double, 2, 1> K5;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100170 K345(x, &K3, &K4, &K5);
171
Austin Schuhec7f06d2019-01-04 07:47:15 +1100172 // Now, solve for all a's and find the best one which meets our criteria.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800173 const Eigen::Matrix<double, 2, 1> C = K3 * v * v + K4 * v;
174 double min_voltage_accel = std::numeric_limits<double>::infinity();
James Kuszmaulea314d92019-02-18 19:45:06 -0800175 double max_voltage_accel = -min_voltage_accel;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800176 for (const double a : {(max_voltage() - C(0, 0)) / K5(0, 0),
177 (max_voltage() - C(1, 0)) / K5(1, 0),
178 (-max_voltage() - C(0, 0)) / K5(0, 0),
179 (-max_voltage() - C(1, 0)) / K5(1, 0)}) {
180 const Eigen::Matrix<double, 2, 1> U = K5 * a + K3 * v * v + K4 * v;
181 if ((U.array().abs() < max_voltage() + 1e-6).all()) {
182 min_voltage_accel = std::min(a, min_voltage_accel);
183 max_voltage_accel = std::max(a, max_voltage_accel);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100184 }
185 }
James Kuszmaulea314d92019-02-18 19:45:06 -0800186 double best_accel = backwards ? min_voltage_accel : max_voltage_accel;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100187
James Kuszmaulea314d92019-02-18 19:45:06 -0800188 double min_friction_accel, max_friction_accel;
189 FrictionLngAccelLimits(x, v, &min_friction_accel, &max_friction_accel);
190 if (backwards) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800191 best_accel = std::max(best_accel, min_friction_accel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800192 } else {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800193 best_accel = std::min(best_accel, max_friction_accel);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100194 }
James Kuszmaulea314d92019-02-18 19:45:06 -0800195
James Kuszmaul66b78042020-02-23 15:30:51 -0800196 // Ideally, the max would never be less than the min, but due to the way that
197 // the runge kutta solver works, it sometimes ticks over the edge.
198 if (max_friction_accel < min_friction_accel) {
199 VLOG(1) << "At x " << x << " v " << v << " min fric acc "
200 << min_friction_accel << " max fric accel " << max_friction_accel;
201 }
202 if (best_accel < min_voltage_accel || best_accel > max_voltage_accel) {
James Kuszmaula9547992024-03-17 17:15:19 -0700203 VLOG(1) << "Viable friction limits and viable voltage limits do not "
204 "overlap (x: "
205 << x << ", v: " << v << ", backwards: " << backwards
206 << ") best_accel = " << best_accel << ", min voltage "
207 << min_voltage_accel << ", max voltage " << max_voltage_accel
208 << " min friction " << min_friction_accel << " max friction "
209 << max_friction_accel << ".";
James Kuszmaul66b78042020-02-23 15:30:51 -0800210
James Kuszmaulea314d92019-02-18 19:45:06 -0800211 // Don't actually do anything--this will just result in attempting to drive
212 // higher voltages thatn we have available. In practice, that'll probably
213 // work out fine.
214 }
215
216 return best_accel;
217}
218
James Kuszmaul75a18c52021-03-10 22:02:07 -0800219double BaseTrajectory::LateralVelocityCurvature(double distance) const {
James Kuszmaulea314d92019-02-18 19:45:06 -0800220 // To calculate these constraints, we first note that:
221 // wheel accels = K2 * v_robot' + K1 * v_robot^2
222 // All that this logic does is solve for v_robot, leaving v_robot' free,
223 // assuming that the wheels are at their limits.
224 // To do this, we:
225 //
226 // 1) Determine what the wheel accels will be at the limit--since we have
227 // two free variables (v_robot, v_robot'), both wheels will be at their
228 // limits--if in a sufficiently tight turn (such that the signs of the
229 // coefficients of K2 are different), then the wheels will be accelerating
230 // in opposite directions; otherwise, they accelerate in the same direction.
231 // The magnitude of these per-wheel accelerations is a function of velocity,
232 // so it must also be solved for.
233 //
234 // 2) Eliminate that v_robot' term (since we don't care
235 // about it) by multiplying be a "K2prime" term (where K2prime * K2 = 0) on
236 // both sides of the equation.
237 //
238 // 3) Solving the relatively tractable remaining equation, which is
239 // basically just grouping all the terms together in one spot and taking the
240 // 4th root of everything.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800241 const double dtheta = spline().DTheta(distance);
242 const Eigen::Matrix<double, 1, 2> K2prime =
James Kuszmaulea314d92019-02-18 19:45:06 -0800243 K2(dtheta).transpose() *
James Kuszmaul75a18c52021-03-10 22:02:07 -0800244 (Eigen::Matrix<double, 2, 2>() << 0, 1, -1, 0).finished();
James Kuszmaulea314d92019-02-18 19:45:06 -0800245 // Calculate whether the wheels are spinning in opposite directions.
246 const bool opposites = K2prime(0) * K2prime(1) < 0;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800247 const Eigen::Matrix<double, 2, 1> K1calc = K1(spline().DDTheta(distance));
248 const double lat_accel_squared = std::pow(dtheta / max_lateral_accel(), 2);
James Kuszmaulea314d92019-02-18 19:45:06 -0800249 const double curvature_change_term =
250 (K2prime * K1calc).value() /
251 (K2prime *
James Kuszmaul75a18c52021-03-10 22:02:07 -0800252 (Eigen::Matrix<double, 2, 1>() << 1.0, (opposites ? -1.0 : 1.0))
James Kuszmaulea314d92019-02-18 19:45:06 -0800253 .finished() *
James Kuszmaul75a18c52021-03-10 22:02:07 -0800254 max_longitudinal_accel())
James Kuszmaulea314d92019-02-18 19:45:06 -0800255 .value();
James Kuszmaul75a18c52021-03-10 22:02:07 -0800256 const double vel_inv = std::sqrt(
257 std::sqrt(std::pow(curvature_change_term, 2) + lat_accel_squared));
James Kuszmaulea314d92019-02-18 19:45:06 -0800258 if (vel_inv == 0.0) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800259 return std::numeric_limits<double>::infinity();
James Kuszmaulea314d92019-02-18 19:45:06 -0800260 }
261 return 1.0 / vel_inv;
262}
263
James Kuszmaul75a18c52021-03-10 22:02:07 -0800264void BaseTrajectory::FrictionLngAccelLimits(double x, double v,
265 double *min_accel,
266 double *max_accel) const {
James Kuszmaulea314d92019-02-18 19:45:06 -0800267 // First, calculate the max longitudinal acceleration that can be achieved
268 // by either wheel given the friction elliipse that we have.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800269 const double lateral_acceleration = v * v * spline().DDXY(x).norm();
James Kuszmaulea314d92019-02-18 19:45:06 -0800270 const double max_wheel_lng_accel_squared =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800271 1.0 - std::pow(lateral_acceleration / max_lateral_accel(), 2.0);
James Kuszmaulea314d92019-02-18 19:45:06 -0800272 if (max_wheel_lng_accel_squared < 0.0) {
James Kuszmaul66b78042020-02-23 15:30:51 -0800273 VLOG(1) << "Something (probably Runge-Kutta) queried invalid velocity " << v
274 << " at distance " << x;
James Kuszmaulea314d92019-02-18 19:45:06 -0800275 // If we encounter this, it means that the Runge-Kutta has attempted to
276 // sample points a bit past the edge of the friction boundary. If so, we
277 // gradually ramp the min/max accels to be more and more incorrect (note
278 // how min_accel > max_accel if we reach this case) to avoid causing any
279 // numerical issues.
280 *min_accel =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800281 std::sqrt(-max_wheel_lng_accel_squared) * max_longitudinal_accel();
James Kuszmaulea314d92019-02-18 19:45:06 -0800282 *max_accel = -*min_accel;
283 return;
284 }
James Kuszmaul75a18c52021-03-10 22:02:07 -0800285 *min_accel = -std::numeric_limits<double>::infinity();
286 *max_accel = std::numeric_limits<double>::infinity();
James Kuszmaulea314d92019-02-18 19:45:06 -0800287
288 // Calculate max/min accelerations by calculating what the robots overall
289 // longitudinal acceleration would be if each wheel were running at the max
290 // forwards/backwards longitudinal acceleration.
291 const double max_wheel_lng_accel =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800292 max_longitudinal_accel() * std::sqrt(max_wheel_lng_accel_squared);
293 const Eigen::Matrix<double, 2, 1> K1v2 = K1(spline().DDTheta(x)) * v * v;
294 const Eigen::Matrix<double, 2, 1> K2inv =
295 K2(spline().DTheta(x)).cwiseInverse();
James Kuszmaulea314d92019-02-18 19:45:06 -0800296 // Store the accelerations of the robot corresponding to each wheel being at
297 // the max/min acceleration. The first coefficient in each vector
298 // corresponds to the left wheel, the second to the right wheel.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800299 const Eigen::Matrix<double, 2, 1> accels1 =
James Kuszmaulea314d92019-02-18 19:45:06 -0800300 K2inv.array() * (-K1v2.array() + max_wheel_lng_accel);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800301 const Eigen::Matrix<double, 2, 1> accels2 =
James Kuszmaulea314d92019-02-18 19:45:06 -0800302 K2inv.array() * (-K1v2.array() - max_wheel_lng_accel);
303
304 // If either term is non-finite, that suggests that a term of K2 is zero
305 // (which is physically possible when turning such that one wheel is
306 // stationary), so just ignore that side of the drivetrain.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800307 if (std::isfinite(accels1(0))) {
James Kuszmaulea314d92019-02-18 19:45:06 -0800308 // The inner max/min in this case determines which of the two cases (+ or
309 // - acceleration on the left wheel) we care about--in a sufficiently
310 // tight turning radius, the left hweel may be accelerating backwards when
311 // the robot as a whole accelerates forwards. We then use that
312 // acceleration to bound the min/max accel.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800313 *min_accel = std::max(*min_accel, std::min(accels1(0), accels2(0)));
314 *max_accel = std::min(*max_accel, std::max(accels1(0), accels2(0)));
James Kuszmaulea314d92019-02-18 19:45:06 -0800315 }
316 // Same logic as previous if-statement, but for the right wheel.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800317 if (std::isfinite(accels1(1))) {
318 *min_accel = std::max(*min_accel, std::min(accels1(1), accels2(1)));
319 *max_accel = std::min(*max_accel, std::max(accels1(1), accels2(1)));
James Kuszmaulea314d92019-02-18 19:45:06 -0800320 }
321}
322
323double Trajectory::VoltageVelocityLimit(
324 double distance, VoltageLimit limit_type,
325 Eigen::Matrix<double, 2, 1> *constraint_voltages) const {
326 // To sketch an outline of the math going on here, we start with the basic
327 // dynamics of the robot along the spline:
328 // K2 * v_robot' + K1 * v_robot^2 = A * K2 * v_robot + B * U
329 // We need to determine the maximum v_robot given constrained U and free
330 // v_robot'.
331 // Similarly to the friction constraints, we accomplish this by first
332 // multiplying by a K2prime term to eliminate the v_robot' term.
333 // As with the friction constraints, we also know that the limits will occur
334 // when both sides of the drivetrain are driven at their max magnitude
335 // voltages, although they may be driven at different signs.
336 // Once we determine whether the voltages match signs, we still have to
337 // consider both possible pairings (technically we could probably
338 // predetermine which pairing, e.g. +/- or -/+, we acre about, but we don't
339 // need to).
340 //
341 // For each pairing, we then get to solve a quadratic formula for the robot
342 // velocity at those voltages. This gives us up to 4 solutions, of which
343 // up to 3 will give us positive velocities; each solution velocity
344 // corresponds to a transition from feasibility to infeasibility, where a
345 // velocity of zero is always feasible, and there will always be 0, 1, or 3
346 // positive solutions. Among the positive solutions, we take both the min
347 // and the max--the min will be the highest velocity such that all
348 // velocities between zero and that velocity are valid; the max will be
349 // the highest feasible velocity. Which we return depends on what the
350 // limit_type is.
351 //
352 // Sketching the actual math:
353 // K2 * v_robot' + K1 * v_robot^2 = A * K2 * v_robot +/- B * U_max
354 // K2prime * K1 * v_robot^2 = K2prime * (A * K2 * v_robot +/- B * U_max)
355 // a v_robot^2 + b v_robot +/- c = 0
James Kuszmaul75a18c52021-03-10 22:02:07 -0800356 const Eigen::Matrix<double, 2, 2> B =
357 velocity_drivetrain().plant().coefficients().B_continuous;
358 const double dtheta = spline().DTheta(distance);
359 const Eigen::Matrix<double, 2, 1> BinvK2 = B.inverse() * K2(dtheta);
James Kuszmaulea314d92019-02-18 19:45:06 -0800360 // Because voltages can actually impact *both* wheels, in order to determine
361 // whether the voltages will have opposite signs, we need to use B^-1 * K2.
362 const bool opposite_voltages = BinvK2(0) * BinvK2(1) > 0.0;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800363 const Eigen::Matrix<double, 1, 2> K2prime =
James Kuszmaulea314d92019-02-18 19:45:06 -0800364 K2(dtheta).transpose() *
James Kuszmaul75a18c52021-03-10 22:02:07 -0800365 (Eigen::Matrix<double, 2, 2>() << 0, 1, -1, 0).finished();
366 const double a = K2prime * K1(spline().DDTheta(distance));
James Kuszmaulea314d92019-02-18 19:45:06 -0800367 const double b = -K2prime *
James Kuszmaul75a18c52021-03-10 22:02:07 -0800368 velocity_drivetrain().plant().coefficients().A_continuous *
James Kuszmaulea314d92019-02-18 19:45:06 -0800369 K2(dtheta);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800370 const Eigen::Matrix<double, 1, 2> c_coeff = -K2prime * B;
James Kuszmaulea314d92019-02-18 19:45:06 -0800371 // Calculate the "positive" version of the voltage limits we will use.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800372 const Eigen::Matrix<double, 2, 1> abs_volts =
373 max_voltage() *
374 (Eigen::Matrix<double, 2, 1>() << 1.0, (opposite_voltages ? -1.0 : 1.0))
James Kuszmaulea314d92019-02-18 19:45:06 -0800375 .finished();
376
James Kuszmaul75a18c52021-03-10 22:02:07 -0800377 double min_valid_vel = std::numeric_limits<double>::infinity();
James Kuszmaulea314d92019-02-18 19:45:06 -0800378 if (limit_type == VoltageLimit::kAggressive) {
379 min_valid_vel = 0.0;
380 }
381 // Iterate over both possibilites for +/- voltage, and solve the quadratic
382 // formula. For every positive solution, adjust the velocity limit
383 // appropriately.
384 for (const double sign : {1.0, -1.0}) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800385 const Eigen::Matrix<double, 2, 1> U = sign * abs_volts;
James Kuszmaulea314d92019-02-18 19:45:06 -0800386 const double prev_vel = min_valid_vel;
387 const double c = c_coeff * U;
388 const double determinant = b * b - 4 * a * c;
389 if (a == 0) {
390 // If a == 0, that implies we are on a constant curvature path, in which
391 // case we just have b * v + c = 0.
392 // Note that if -b * c > 0.0, then vel will be greater than zero and b
393 // will be non-zero.
394 if (-b * c > 0.0) {
395 const double vel = -c / b;
396 if (limit_type == VoltageLimit::kConservative) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800397 min_valid_vel = std::min(min_valid_vel, vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800398 } else {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800399 min_valid_vel = std::max(min_valid_vel, vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800400 }
401 } else if (b == 0) {
402 // If a and b are zero, then we are travelling in a straight line and
403 // have no voltage-based velocity constraints.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800404 min_valid_vel = std::numeric_limits<double>::infinity();
James Kuszmaulea314d92019-02-18 19:45:06 -0800405 }
406 } else if (determinant > 0) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800407 const double sqrt_determinant = std::sqrt(determinant);
James Kuszmaulea314d92019-02-18 19:45:06 -0800408 const double high_vel = (-b + sqrt_determinant) / (2.0 * a);
409 const double low_vel = (-b - sqrt_determinant) / (2.0 * a);
410 if (low_vel > 0) {
411 if (limit_type == VoltageLimit::kConservative) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800412 min_valid_vel = std::min(min_valid_vel, low_vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800413 } else {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800414 min_valid_vel = std::max(min_valid_vel, low_vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800415 }
416 }
417 if (high_vel > 0) {
418 if (limit_type == VoltageLimit::kConservative) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800419 min_valid_vel = std::min(min_valid_vel, high_vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800420 } else {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800421 min_valid_vel = std::max(min_valid_vel, high_vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800422 }
423 }
424 } else if (determinant == 0 && -b * a > 0) {
425 const double vel = -b / (2.0 * a);
426 if (vel > 0.0) {
427 if (limit_type == VoltageLimit::kConservative) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800428 min_valid_vel = std::min(min_valid_vel, vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800429 } else {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800430 min_valid_vel = std::max(min_valid_vel, vel);
James Kuszmaulea314d92019-02-18 19:45:06 -0800431 }
432 }
433 }
434 if (constraint_voltages != nullptr && prev_vel != min_valid_vel) {
435 *constraint_voltages = U;
436 }
437 }
438 return min_valid_vel;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100439}
440
441void Trajectory::ForwardPass() {
442 plan_[0] = 0.0;
443 const double delta_distance = Distance(1) - Distance(0);
444 for (size_t i = 0; i < plan_.size() - 1; ++i) {
445 const double distance = Distance(i);
446
447 // Integrate our acceleration forward one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800448 const double new_plan_velocity = IntegrateAccelForDistance(
449 [this](double x, double v) { return ForwardAcceleration(x, v); },
450 plan_[i], distance, delta_distance);
451
James Kuszmaulea314d92019-02-18 19:45:06 -0800452 if (new_plan_velocity <= plan_[i + 1]) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800453 plan_[i + 1] = new_plan_velocity;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800454 plan_segment_type_[i] = fb::SegmentConstraint::ACCELERATION_LIMITED;
Austin Schuhe73a9052019-01-07 12:16:17 -0800455 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100456 }
457}
458
Austin Schuhec7f06d2019-01-04 07:47:15 +1100459void Trajectory::BackwardPass() {
460 const double delta_distance = Distance(0) - Distance(1);
461 plan_.back() = 0.0;
462 for (size_t i = plan_.size() - 1; i > 0; --i) {
463 const double distance = Distance(i);
464
465 // Integrate our deceleration back one step.
Austin Schuhe73a9052019-01-07 12:16:17 -0800466 const double new_plan_velocity = IntegrateAccelForDistance(
467 [this](double x, double v) { return BackwardAcceleration(x, v); },
468 plan_[i], distance, delta_distance);
469
James Kuszmaulea314d92019-02-18 19:45:06 -0800470 if (new_plan_velocity <= plan_[i - 1]) {
Austin Schuhe73a9052019-01-07 12:16:17 -0800471 plan_[i - 1] = new_plan_velocity;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800472 plan_segment_type_[i - 1] = fb::SegmentConstraint::DECELERATION_LIMITED;
Austin Schuhe73a9052019-01-07 12:16:17 -0800473 }
Austin Schuhec7f06d2019-01-04 07:47:15 +1100474 }
475}
476
James Kuszmaul75a18c52021-03-10 22:02:07 -0800477Eigen::Matrix<double, 3, 1> BaseTrajectory::FFAcceleration(
478 double distance) const {
Austin Schuhe73a9052019-01-07 12:16:17 -0800479 if (distance < 0.0) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100480 // Make sure we don't end up off the beginning of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800481 distance = 0.0;
482 } else if (distance > length()) {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100483 // Make sure we don't end up off the end of the curve.
Austin Schuhe73a9052019-01-07 12:16:17 -0800484 distance = length();
Austin Schuhec7f06d2019-01-04 07:47:15 +1100485 }
Austin Schuhe73a9052019-01-07 12:16:17 -0800486 const size_t before_index = DistanceToSegment(distance);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800487 const size_t after_index =
488 std::min(before_index + 1, distance_plan_size() - 1);
Austin Schuhe73a9052019-01-07 12:16:17 -0800489
Austin Schuhec7f06d2019-01-04 07:47:15 +1100490 const double before_distance = Distance(before_index);
491 const double after_distance = Distance(after_index);
492
Austin Schuhec7f06d2019-01-04 07:47:15 +1100493 // And then also make sure we aren't curvature limited.
494 const double vcurvature = LateralVelocityCurvature(distance);
495
496 double acceleration;
497 double velocity;
James Kuszmaulea314d92019-02-18 19:45:06 -0800498 // TODO(james): While technically correct for sufficiently small segment
499 // steps, this method of switching between limits has a tendency to produce
500 // sudden jumps in acceelrations, which is undesirable.
James Kuszmaul75a18c52021-03-10 22:02:07 -0800501 switch (plan_constraint(DistanceToSegment(distance))) {
502 case fb::SegmentConstraint::VELOCITY_LIMITED:
Austin Schuhe73a9052019-01-07 12:16:17 -0800503 acceleration = 0.0;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800504 velocity =
505 (plan_velocity(before_index) + plan_velocity(after_index)) / 2.0;
Austin Schuhe73a9052019-01-07 12:16:17 -0800506 // TODO(austin): Accelerate or decelerate until we hit the limit in the
507 // time slice. Otherwise our acceleration will be lying for this slice.
508 // Do note, we've got small slices so the effect will be small.
509 break;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800510 case fb::SegmentConstraint::CURVATURE_LIMITED:
Austin Schuhe73a9052019-01-07 12:16:17 -0800511 velocity = vcurvature;
James Kuszmaulea314d92019-02-18 19:45:06 -0800512 FrictionLngAccelLimits(distance, velocity, &acceleration, &acceleration);
513 break;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800514 case fb::SegmentConstraint::VOLTAGE_LIMITED:
James Kuszmaulea314d92019-02-18 19:45:06 -0800515 // Normally, we expect that voltage limited plans will all get dominated
516 // by the acceleration/deceleration limits. This may not always be true;
517 // if we ever encounter this error, we just need to back out what the
518 // accelerations would be in this case.
Austin Schuhd749d932020-12-30 21:38:40 -0800519 LOG(FATAL) << "Unexpectedly got VOLTAGE_LIMITED plan.";
Austin Schuhe73a9052019-01-07 12:16:17 -0800520 break;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800521 case fb::SegmentConstraint::ACCELERATION_LIMITED:
James Kuszmaulea314d92019-02-18 19:45:06 -0800522 // TODO(james): The integration done here and in the DECELERATION_LIMITED
523 // can technically cause us to violate friction constraints. We currently
524 // don't do anything about it to avoid causing sudden jumps in voltage,
525 // but we probably *should* at some point.
Austin Schuhe73a9052019-01-07 12:16:17 -0800526 velocity = IntegrateAccelForDistance(
527 [this](double x, double v) { return ForwardAcceleration(x, v); },
James Kuszmaul75a18c52021-03-10 22:02:07 -0800528 plan_velocity(before_index), before_distance,
529 distance - before_distance);
Austin Schuhe73a9052019-01-07 12:16:17 -0800530 acceleration = ForwardAcceleration(distance, velocity);
531 break;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800532 case fb::SegmentConstraint::DECELERATION_LIMITED:
Austin Schuhe73a9052019-01-07 12:16:17 -0800533 velocity = IntegrateAccelForDistance(
534 [this](double x, double v) { return BackwardAcceleration(x, v); },
James Kuszmaul75a18c52021-03-10 22:02:07 -0800535 plan_velocity(after_index), after_distance,
536 distance - after_distance);
Austin Schuhe73a9052019-01-07 12:16:17 -0800537 acceleration = BackwardAcceleration(distance, velocity);
538 break;
539 default:
James Kuszmaul75a18c52021-03-10 22:02:07 -0800540 AOS_LOG(FATAL, "Unknown segment type %d\n",
541 static_cast<int>(plan_constraint(DistanceToSegment(distance))));
Austin Schuhe73a9052019-01-07 12:16:17 -0800542 break;
543 }
544
James Kuszmaul75a18c52021-03-10 22:02:07 -0800545 return (Eigen::Matrix<double, 3, 1>() << distance, velocity, acceleration)
Austin Schuhec7f06d2019-01-04 07:47:15 +1100546 .finished();
547}
548
James Kuszmaul75a18c52021-03-10 22:02:07 -0800549size_t FinishedTrajectory::distance_plan_size() const {
550 return trajectory().has_distance_based_plan()
551 ? trajectory().distance_based_plan()->size()
552 : 0u;
553}
554
555fb::SegmentConstraint FinishedTrajectory::plan_constraint(size_t index) const {
556 CHECK_LT(index, distance_plan_size());
557 return trajectory().distance_based_plan()->Get(index)->segment_constraint();
558}
559
560float FinishedTrajectory::plan_velocity(size_t index) const {
561 CHECK_LT(index, distance_plan_size());
562 return trajectory().distance_based_plan()->Get(index)->velocity();
563}
564
565Eigen::Matrix<double, 2, 1> BaseTrajectory::FFVoltage(double distance) const {
Austin Schuhec7f06d2019-01-04 07:47:15 +1100566 const Eigen::Matrix<double, 3, 1> xva = FFAcceleration(distance);
567 const double velocity = xva(1);
568 const double acceleration = xva(2);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100569
James Kuszmaul75a18c52021-03-10 22:02:07 -0800570 Eigen::Matrix<double, 2, 1> K3;
571 Eigen::Matrix<double, 2, 1> K4;
572 Eigen::Matrix<double, 2, 1> K5;
Austin Schuhe73a9052019-01-07 12:16:17 -0800573 K345(distance, &K3, &K4, &K5);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100574
575 return K5 * acceleration + K3 * velocity * velocity + K4 * velocity;
576}
577
James Kuszmaul75a18c52021-03-10 22:02:07 -0800578const std::vector<double> Trajectory::Distances() const {
579 std::vector<double> d;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100580 d.reserve(plan_.size());
581 for (size_t i = 0; i < plan_.size(); ++i) {
582 d.push_back(Distance(i));
583 }
584 return d;
585}
586
James Kuszmaul75a18c52021-03-10 22:02:07 -0800587Eigen::Matrix<double, 3, 1> BaseTrajectory::GetNextXVA(
588 std::chrono::nanoseconds dt, Eigen::Matrix<double, 2, 1> *state) const {
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700589 double dt_float = ::aos::time::DurationInSeconds(dt);
Austin Schuhec7f06d2019-01-04 07:47:15 +1100590
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800591 const double last_distance = (*state)(0);
Alex Perry4ae2fd72019-02-03 15:55:57 -0800592 // TODO(austin): This feels like something that should be pulled out into
593 // a library for re-use.
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700594 *state = RungeKutta(
James Kuszmaul75a18c52021-03-10 22:02:07 -0800595 [this](const Eigen::Matrix<double, 2, 1> x) {
596 Eigen::Matrix<double, 3, 1> xva = FFAcceleration(x(0));
597 return (Eigen::Matrix<double, 2, 1>() << x(1), xva(2)).finished();
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700598 },
599 *state, dt_float);
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800600 // Force the distance to move forwards, to guarantee that we actually finish
601 // the planning.
602 constexpr double kMinDistanceIncrease = 1e-7;
603 if ((*state)(0) < last_distance + kMinDistanceIncrease) {
604 (*state)(0) = last_distance + kMinDistanceIncrease;
605 }
Alex Perry4ae2fd72019-02-03 15:55:57 -0800606
James Kuszmaul75a18c52021-03-10 22:02:07 -0800607 Eigen::Matrix<double, 3, 1> result = FFAcceleration((*state)(0));
Alex Perry4ae2fd72019-02-03 15:55:57 -0800608 (*state)(1) = result(1);
609 return result;
610}
611
James Kuszmaul75a18c52021-03-10 22:02:07 -0800612std::vector<Eigen::Matrix<double, 3, 1>> Trajectory::PlanXVA(
613 std::chrono::nanoseconds dt) {
614 Eigen::Matrix<double, 2, 1> state = Eigen::Matrix<double, 2, 1>::Zero();
615 std::vector<Eigen::Matrix<double, 3, 1>> result;
Austin Schuhec7f06d2019-01-04 07:47:15 +1100616 result.emplace_back(FFAcceleration(0));
617 result.back()(1) = 0.0;
618
Alex Perry4ae2fd72019-02-03 15:55:57 -0800619 while (!is_at_end(state)) {
James Kuszmaul4d3c2642020-03-05 07:32:39 -0800620 if (state_is_faulted(state)) {
621 LOG(WARNING)
622 << "Found invalid state in generating spline and aborting. This is "
623 "likely due to a spline with extremely high jerk/changes in "
624 "curvature with an insufficiently small step size.";
625 return {};
626 }
Alex Perry4ae2fd72019-02-03 15:55:57 -0800627 result.emplace_back(GetNextXVA(dt, &state));
Austin Schuhec7f06d2019-01-04 07:47:15 +1100628 }
629 return result;
630}
631
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800632void Trajectory::LimitVelocity(double starting_distance, double ending_distance,
633 const double max_velocity) {
634 const double segment_length = ending_distance - starting_distance;
635
636 const double min_length = length() / static_cast<double>(plan_.size() - 1);
637 if (starting_distance > ending_distance) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700638 AOS_LOG(FATAL, "End before start: %f > %f\n", starting_distance,
639 ending_distance);
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800640 }
James Kuszmaul75a18c52021-03-10 22:02:07 -0800641 starting_distance = std::min(length(), std::max(0.0, starting_distance));
642 ending_distance = std::min(length(), std::max(0.0, ending_distance));
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800643 if (segment_length < min_length) {
644 const size_t plan_index = static_cast<size_t>(
James Kuszmaul75a18c52021-03-10 22:02:07 -0800645 std::round((starting_distance + ending_distance) / 2.0 / min_length));
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800646 if (max_velocity < plan_[plan_index]) {
647 plan_[plan_index] = max_velocity;
648 }
649 } else {
650 for (size_t i = DistanceToSegment(starting_distance) + 1;
651 i < DistanceToSegment(ending_distance) + 1; ++i) {
652 if (max_velocity < plan_[i]) {
653 plan_[i] = max_velocity;
654 if (i < DistanceToSegment(ending_distance)) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800655 plan_segment_type_[i] = fb::SegmentConstraint::VELOCITY_LIMITED;
Austin Schuh5b9e9c22019-01-07 15:44:06 -0800656 }
657 }
658 }
659 }
660}
661
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700662void Trajectory::PathRelativeContinuousSystem(double distance,
663 Eigen::Matrix<double, 5, 5> *A,
664 Eigen::Matrix<double, 5, 2> *B) {
665 const double nominal_velocity = FFAcceleration(distance)(1);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800666 const double dtheta_dt = spline().DThetaDt(distance, nominal_velocity);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700667 // Calculate the "path-relative" coordinates, which are:
668 // [[distance along the path],
669 // [lateral position along path],
670 // [theta],
671 // [left wheel velocity],
672 // [right wheel velocity]]
673 Eigen::Matrix<double, 5, 1> nominal_X;
674 nominal_X << distance, 0.0, 0.0,
James Kuszmaul75a18c52021-03-10 22:02:07 -0800675 nominal_velocity - dtheta_dt * robot_radius_l(),
676 nominal_velocity + dtheta_dt * robot_radius_r();
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700677 PathRelativeContinuousSystem(nominal_X, A, B);
678}
679
680void Trajectory::PathRelativeContinuousSystem(
681 const Eigen::Matrix<double, 5, 1> &X, Eigen::Matrix<double, 5, 5> *A,
682 Eigen::Matrix<double, 5, 2> *B) {
683 A->setZero();
684 B->setZero();
685 const double theta = X(2);
686 const double ctheta = std::cos(theta);
687 const double stheta = std::sin(theta);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800688 const double curvature = spline().DTheta(X(0));
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700689 const double longitudinal_velocity = (X(3) + X(4)) / 2.0;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800690 const double diameter = robot_radius_l() + robot_radius_r();
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700691 // d_dpath / dt = (v_left + v_right) / 2.0 * cos(theta)
692 // (d_dpath / dt) / dv_left = cos(theta) / 2.0
693 (*A)(0, 3) = ctheta / 2.0;
694 // (d_dpath / dt) / dv_right = cos(theta) / 2.0
695 (*A)(0, 4) = ctheta / 2.0;
696 // (d_dpath / dt) / dtheta = -(v_left + v_right) / 2.0 * sin(theta)
697 (*A)(0, 2) = -longitudinal_velocity * stheta;
698 // d_dlat / dt = (v_left + v_right) / 2.0 * sin(theta)
699 // (d_dlat / dt) / dv_left = sin(theta) / 2.0
700 (*A)(1, 3) = stheta / 2.0;
701 // (d_dlat / dt) / dv_right = sin(theta) / 2.0
702 (*A)(1, 4) = stheta / 2.0;
703 // (d_dlat / dt) / dtheta = (v_left + v_right) / 2.0 * cos(theta)
704 (*A)(1, 2) = longitudinal_velocity * ctheta;
705 // dtheta / dt = (v_right - v_left) / diameter - curvature * (v_left +
706 // v_right) / 2.0
707 // (dtheta / dt) / dv_left = -1.0 / diameter - curvature / 2.0
708 (*A)(2, 3) = -1.0 / diameter - curvature / 2.0;
709 // (dtheta / dt) / dv_right = 1.0 / diameter - curvature / 2.0
710 (*A)(2, 4) = 1.0 / diameter - curvature / 2.0;
711 // v_{left,right} / dt = the normal LTI system.
712 A->block<2, 2>(3, 3) =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800713 velocity_drivetrain().plant().coefficients().A_continuous;
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700714 B->block<2, 2>(3, 0) =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800715 velocity_drivetrain().plant().coefficients().B_continuous;
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700716}
717
718double Trajectory::EstimateDistanceAlongPath(
719 double nominal_distance, const Eigen::Matrix<double, 5, 1> &state) {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800720 const double nominal_theta = spline().Theta(nominal_distance);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700721 const Eigen::Matrix<double, 2, 1> xy_err =
James Kuszmaul75a18c52021-03-10 22:02:07 -0800722 state.block<2, 1>(0, 0) - spline().XY(nominal_distance);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700723 return nominal_distance + xy_err.x() * std::cos(nominal_theta) +
724 xy_err.y() * std::sin(nominal_theta);
725}
726
James Kuszmaul75a18c52021-03-10 22:02:07 -0800727Eigen::Matrix<double, 5, 1> FinishedTrajectory::StateToPathRelativeState(
James Kuszmaul5e8ce312021-03-27 14:59:17 -0700728 double distance, const Eigen::Matrix<double, 5, 1> &state,
729 bool drive_backwards) const {
James Kuszmaul75a18c52021-03-10 22:02:07 -0800730 const double nominal_theta = spline().Theta(distance);
731 const Eigen::Matrix<double, 2, 1> nominal_xy = spline().XY(distance);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700732 const Eigen::Matrix<double, 2, 1> xy_err =
733 state.block<2, 1>(0, 0) - nominal_xy;
734 const double ctheta = std::cos(nominal_theta);
735 const double stheta = std::sin(nominal_theta);
736 Eigen::Matrix<double, 5, 1> path_state;
737 path_state(0) = distance + xy_err.x() * ctheta + xy_err.y() * stheta;
738 path_state(1) = -xy_err.x() * stheta + xy_err.y() * ctheta;
James Kuszmaul5e8ce312021-03-27 14:59:17 -0700739 path_state(2) = aos::math::NormalizeAngle(state(2) - nominal_theta +
740 (drive_backwards ? M_PI : 0.0));
741 path_state(2) = aos::math::NormalizeAngle(state(2) - nominal_theta);
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700742 path_state(3) = state(3);
743 path_state(4) = state(4);
James Kuszmaul5e8ce312021-03-27 14:59:17 -0700744 if (drive_backwards) {
745 std::swap(path_state(3), path_state(4));
746 path_state(3) *= -1.0;
747 path_state(4) *= -1.0;
748 }
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700749 return path_state;
750}
751
752// Path-relative controller method:
753// For the path relative controller, we use a non-standard version of LQR to
754// perform the control. Essentially, we first transform the system into
755// a set of path-relative coordinates (where the reference that we use is the
756// desired path reference). This gives us a system that is linear and
757// time-varying, i.e. the system is a set of A_k, B_k matrices for each
758// timestep k.
759// In order to control this, we use a discrete-time finite-horizon LQR, using
760// the appropraite [AB]_k for the given timestep. Note that the finite-horizon
761// LQR requires choosing a terminal cost (i.e., what the cost should be
762// for if we have not precisely reached the goal at the end of the time-period).
763// For this, I approximate the infinite-horizon LQR solution by extending the
764// finite-horizon much longer (albeit with the extension just using the
765// linearization for the infal point).
766void Trajectory::CalculatePathGains() {
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800767 const std::vector<Eigen::Matrix<double, 3, 1>> xva_plan =
768 PlanXVA(config_->dt);
James Kuszmaulc3eaa472021-03-03 19:43:45 -0800769 if (xva_plan.empty()) {
770 LOG(ERROR) << "Plan is empty--unable to plan trajectory.";
771 return;
772 }
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700773 plan_gains_.resize(xva_plan.size());
774
775 // Set up reasonable gain matrices. Current choices of gains are arbitrary
776 // and just setup to work well enough for the simulation tests.
James Kuszmaul6e5f8252024-03-17 21:00:08 -0700777 Eigen::Matrix<double, 5, 5> Q = config_->spline_follower_config.Q;
778 Eigen::Matrix<double, 2, 2> R = config_->spline_follower_config.R;
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700779
780 Eigen::Matrix<double, 5, 5> P = Q;
781
782 CHECK_LT(0u, xva_plan.size());
783 const int max_index = static_cast<int>(xva_plan.size()) - 1;
784 for (int i = max_index; i >= 0; --i) {
785 const double distance = xva_plan[i](0);
786 Eigen::Matrix<double, 5, 5> A_continuous;
787 Eigen::Matrix<double, 5, 2> B_continuous;
788 PathRelativeContinuousSystem(distance, &A_continuous, &B_continuous);
789 Eigen::Matrix<double, 5, 5> A_discrete;
790 Eigen::Matrix<double, 5, 2> B_discrete;
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800791 controls::C2D(A_continuous, B_continuous, config_->dt, &A_discrete,
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700792 &B_discrete);
793
794 if (i == max_index) {
795 // At the final timestep, approximate P by iterating a bunch of times.
796 // This is terminal cost mentioned in function-level comments.
797 // This does a very loose job of solving the DARE. Ideally, we would
798 // actually use a DARE solver directly, but based on some initial testing,
799 // this method is a bit more robust (or, at least, it is a bit more robust
800 // if we don't want to spend more time handling the potential error
801 // cases the DARE solver can encounter).
802 constexpr int kExtraIters = 100;
803 for (int jj = 0; jj < kExtraIters; ++jj) {
804 const Eigen::Matrix<double, 5, 5> AP = A_discrete.transpose() * P;
805 const Eigen::Matrix<double, 5, 2> APB = AP * B_discrete;
806 const Eigen::Matrix<double, 2, 2> RBPBinv =
807 (R + B_discrete.transpose() * P * B_discrete).inverse();
808 P = AP * A_discrete - APB * RBPBinv * APB.transpose() + Q;
809 }
810 }
811
812 const Eigen::Matrix<double, 5, 5> AP = A_discrete.transpose() * P;
813 const Eigen::Matrix<double, 5, 2> APB = AP * B_discrete;
814 const Eigen::Matrix<double, 2, 2> RBPBinv =
815 (R + B_discrete.transpose() * P * B_discrete).inverse();
816 plan_gains_[i].first = distance;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800817 const Eigen::Matrix<double, 2, 5> K = RBPBinv * APB.transpose();
818 plan_gains_[i].second = K.cast<float>();
819 P = AP * A_discrete - APB * K + Q;
James Kuszmaul5d917822024-03-17 19:59:31 -0700820 CHECK_LT(P.norm(), 1e30) << "LQR calculations became unstable.";
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700821 }
822}
823
James Kuszmaul75a18c52021-03-10 22:02:07 -0800824Eigen::Matrix<double, 2, 5> FinishedTrajectory::GainForDistance(
825 double distance) const {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700826 CHECK(trajectory().gains() != nullptr);
James Kuszmaul75a18c52021-03-10 22:02:07 -0800827 const flatbuffers::Vector<flatbuffers::Offset<fb::GainPoint>> &gains =
Austin Schuh6bdcc372024-06-27 14:49:11 -0700828 *trajectory().gains();
James Kuszmaul75a18c52021-03-10 22:02:07 -0800829 CHECK_LT(0u, gains.size());
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700830 size_t index = 0;
James Kuszmaul75a18c52021-03-10 22:02:07 -0800831 for (index = 0; index < gains.size() - 1; ++index) {
832 if (gains[index + 1]->distance() > distance) {
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700833 break;
834 }
835 }
James Kuszmaul75a18c52021-03-10 22:02:07 -0800836 // ColMajor is the default storage order, but call it out explicitly here.
837 return Eigen::Matrix<float, 2, 5, Eigen::ColMajor>{
838 gains[index]->gains()->data()}
839 .cast<double>();
840}
841
842namespace {
843flatbuffers::Offset<Constraint> MakeWholeLengthConstraint(
844 flatbuffers::FlatBufferBuilder *fbb, ConstraintType constraint_type,
845 float value) {
846 Constraint::Builder builder(*fbb);
847 builder.add_constraint_type(constraint_type);
848 builder.add_value(value);
849 return builder.Finish();
850}
851} // namespace
852
853flatbuffers::Offset<fb::Trajectory> Trajectory::Serialize(
854 flatbuffers::FlatBufferBuilder *fbb) const {
855 std::array<flatbuffers::Offset<Constraint>, 3> constraints_offsets = {
856 MakeWholeLengthConstraint(fbb, ConstraintType::LONGITUDINAL_ACCELERATION,
857 max_longitudinal_accel()),
858 MakeWholeLengthConstraint(fbb, ConstraintType::LATERAL_ACCELERATION,
859 max_lateral_accel()),
860 MakeWholeLengthConstraint(fbb, ConstraintType::VOLTAGE, max_voltage())};
861 const auto constraints = fbb->CreateVector<Constraint>(
862 constraints_offsets.data(), constraints_offsets.size());
863 const flatbuffers::Offset<fb::DistanceSpline> spline_offset =
864 spline().Serialize(fbb, constraints);
865
866 std::vector<flatbuffers::Offset<fb::PlanPoint>> plan_points;
867 for (size_t ii = 0; ii < distance_plan_size(); ++ii) {
868 plan_points.push_back(fb::CreatePlanPoint(
869 *fbb, Distance(ii), plan_velocity(ii), plan_constraint(ii)));
870 }
871
872 // TODO(james): What is an appropriate cap?
873 CHECK_LT(plan_gains_.size(), 5000u);
874 CHECK_LT(0u, plan_gains_.size());
875 std::vector<flatbuffers::Offset<fb::GainPoint>> gain_points;
876 const size_t matrix_size = plan_gains_[0].second.size();
877 for (size_t ii = 0; ii < plan_gains_.size(); ++ii) {
878 gain_points.push_back(fb::CreateGainPoint(
879 *fbb, plan_gains_[ii].first,
880 fbb->CreateVector(plan_gains_[ii].second.data(), matrix_size)));
881 }
882
883 return fb::CreateTrajectory(*fbb, spline_idx_, fbb->CreateVector(plan_points),
884 fbb->CreateVector(gain_points), spline_offset,
885 drive_spline_backwards_);
886}
887
888float BaseTrajectory::ConstraintValue(
889 const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
890 ConstraintType type) {
891 if (constraints != nullptr) {
892 for (const Constraint *constraint : *constraints) {
893 if (constraint->constraint_type() == type) {
894 return constraint->value();
895 }
896 }
897 }
898 return DefaultConstraint(type);
899}
900
901const Eigen::Matrix<double, 5, 1> BaseTrajectory::GoalState(
902 double distance, double velocity) const {
903 Eigen::Matrix<double, 5, 1> result;
904 result.block<2, 1>(0, 0) = spline().XY(distance);
905 result(2, 0) = spline().Theta(distance);
906
907 result.block<2, 1>(3, 0) =
James Kuszmaul5c4ccf62024-03-03 17:29:49 -0800908 config_->Tla_to_lr() * (Eigen::Matrix<double, 2, 1>() << velocity,
909 spline().DThetaDt(distance, velocity))
910 .finished();
James Kuszmaul75a18c52021-03-10 22:02:07 -0800911 return result;
James Kuszmaulaa2499d2020-06-02 21:31:19 -0700912}
913
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800914} // namespace frc971::control_loops::drivetrain