WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
TrapezoidProfile.hpp
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <type_traits>
8
10#include "wpi/units/math.hpp"
11#include "wpi/units/time.hpp"
12
13namespace wpi::math {
14
15/**
16 * A trapezoid-shaped velocity profile.
17 *
18 * While this class can be used for a profiled movement from start to finish,
19 * the intended usage is to filter a reference's dynamics based on trapezoidal
20 * velocity constraints. To compute the reference obeying this constraint, do
21 * the following.
22 *
23 * Initialization:
24 * @code{.cpp}
25 * TrapezoidProfile::Constraints constraints{kMaxV, kMaxA};
26 * double previousProfiledReference = initialReference;
27 * TrapezoidProfile profile{constraints};
28 * @endcode
29 *
30 * Run on update:
31 * @code{.cpp}
32 * previousProfiledReference = profile.Calculate(timeSincePreviousUpdate,
33 * previousProfiledReference,
34 * unprofiledReference);
35 * @endcode
36 *
37 * where `unprofiledReference` is free to change between calls. Note that when
38 * the unprofiled reference is within the constraints, `Calculate()` returns the
39 * unprofiled reference unchanged.
40 *
41 * Otherwise, a timer can be started to provide monotonic values for
42 * `Calculate()` and to determine when the profile has completed via
43 * `IsFinished()`.
44 */
45template <class Distance>
47 public:
48 using Distance_t = wpi::units::unit_t<Distance>;
49 using Velocity =
50 wpi::units::compound_unit<Distance,
51 wpi::units::inverse<wpi::units::seconds>>;
52 using Velocity_t = wpi::units::unit_t<Velocity>;
54 wpi::units::compound_unit<Velocity,
55 wpi::units::inverse<wpi::units::seconds>>;
56 using Acceleration_t = wpi::units::unit_t<Acceleration>;
57
58 /**
59 * Profile constraints.
60 */
62 public:
63 /// Maximum velocity.
65
66 /// Maximum acceleration.
68
69 /**
70 * Default constructor.
71 */
72 constexpr Constraints() {
73 if (!std::is_constant_evaluated()) {
74 wpi::math::MathSharedStore::ReportUsage("TrapezoidProfile", "");
75 }
76 }
77
78 /**
79 * Constructs constraints for a Trapezoid Profile.
80 *
81 * @param maxVelocity Maximum velocity, must be non-negative.
82 * @param maxAcceleration Maximum acceleration, must be non-negative.
83 */
87 if (!std::is_constant_evaluated()) {
88 wpi::math::MathSharedStore::ReportUsage("TrapezoidProfile", "");
89 }
90
92 throw std::domain_error("Constraints must be non-negative");
93 }
94 }
95 };
96
97 /**
98 * Profile state.
99 */
100 class State {
101 public:
102 /// The position at this state.
104
105 /// The velocity at this state.
107
108 constexpr bool operator==(const State&) const = default;
109 };
110
111 /**
112 * Constructs a TrapezoidProfile.
113 *
114 * @param constraints The constraints on the profile, like maximum velocity.
115 */
116 constexpr TrapezoidProfile(Constraints constraints) // NOLINT
117 : m_constraints(constraints) {}
118
119 constexpr TrapezoidProfile(const TrapezoidProfile&) = default;
120 constexpr TrapezoidProfile& operator=(const TrapezoidProfile&) = default;
121 constexpr TrapezoidProfile(TrapezoidProfile&&) = default;
123
124 /**
125 * Calculates the position and velocity for the profile at a time t where the
126 * current state is at time t = 0.
127 *
128 * @param t How long to advance from the current state toward the desired
129 * state.
130 * @param current The current state.
131 * @param goal The desired state when the profile is complete.
132 * @return The position and velocity of the profile at time t.
133 */
134 constexpr State Calculate(wpi::units::second_t t, State current, State goal) {
135 m_direction = ShouldFlipAcceleration(current, goal) ? -1 : 1;
136 m_current = Direct(current);
137 goal = Direct(goal);
138 if (wpi::units::math::abs(m_current.velocity) > m_constraints.maxVelocity) {
139 m_current.velocity = wpi::units::math::copysign(m_constraints.maxVelocity,
140 m_current.velocity);
141 }
142
143 // Deal with a possibly truncated motion profile (with nonzero initial or
144 // final velocity) by calculating the parameters as if the profile began and
145 // ended at zero velocity
146 wpi::units::second_t cutoffBegin =
147 m_current.velocity / m_constraints.maxAcceleration;
148 Distance_t cutoffDistBegin =
149 cutoffBegin * cutoffBegin * m_constraints.maxAcceleration / 2.0;
150
151 wpi::units::second_t cutoffEnd =
152 goal.velocity / m_constraints.maxAcceleration;
153 Distance_t cutoffDistEnd =
154 cutoffEnd * cutoffEnd * m_constraints.maxAcceleration / 2.0;
155
156 // Now we can calculate the parameters as if it was a full trapezoid instead
157 // of a truncated one
158
159 Distance_t fullTrapezoidDist =
160 cutoffDistBegin + (goal.position - m_current.position) + cutoffDistEnd;
161 wpi::units::second_t accelerationTime =
162 m_constraints.maxVelocity / m_constraints.maxAcceleration;
163
164 Distance_t fullVelocityDist =
165 fullTrapezoidDist -
166 accelerationTime * accelerationTime * m_constraints.maxAcceleration;
167
168 // Handle the case where the profile never reaches full velocity
169 if (fullVelocityDist < Distance_t{0}) {
170 accelerationTime = wpi::units::math::sqrt(fullTrapezoidDist /
171 m_constraints.maxAcceleration);
172 fullVelocityDist = Distance_t{0};
173 }
174
175 m_endAccel = accelerationTime - cutoffBegin;
176 m_endFullVelocity =
177 m_endAccel + fullVelocityDist / m_constraints.maxVelocity;
178 m_endDecel = m_endFullVelocity + accelerationTime - cutoffEnd;
179 State result = m_current;
180
181 if (t < m_endAccel) {
182 result.velocity += t * m_constraints.maxAcceleration;
183 result.position +=
184 (m_current.velocity + t * m_constraints.maxAcceleration / 2.0) * t;
185 } else if (t < m_endFullVelocity) {
186 result.velocity = m_constraints.maxVelocity;
187 result.position += (m_current.velocity +
188 m_endAccel * m_constraints.maxAcceleration / 2.0) *
189 m_endAccel +
190 m_constraints.maxVelocity * (t - m_endAccel);
191 } else if (t <= m_endDecel) {
192 result.velocity =
193 goal.velocity + (m_endDecel - t) * m_constraints.maxAcceleration;
194 wpi::units::second_t timeLeft = m_endDecel - t;
195 result.position =
196 goal.position -
197 (goal.velocity + timeLeft * m_constraints.maxAcceleration / 2.0) *
198 timeLeft;
199 } else {
200 result = goal;
201 }
202
203 return Direct(result);
204 }
205
206 /**
207 * Returns the time left until a target distance in the profile is reached.
208 *
209 * @param target The target distance.
210 * @return The time left until a target distance in the profile is reached, or
211 * zero if no goal was set.
212 */
213 constexpr wpi::units::second_t TimeLeftUntil(Distance_t target) const {
214 Distance_t position = m_current.position * m_direction;
215 Velocity_t velocity = m_current.velocity * m_direction;
216
217 wpi::units::second_t endAccel = m_endAccel * m_direction;
218 wpi::units::second_t endFullVelocity =
219 m_endFullVelocity * m_direction - endAccel;
220
221 if (target < position) {
222 endAccel *= -1.0;
223 endFullVelocity *= -1.0;
224 velocity *= -1.0;
225 }
226
227 endAccel = wpi::units::math::max(endAccel, 0_s);
228 endFullVelocity = wpi::units::math::max(endFullVelocity, 0_s);
229
230 const Acceleration_t acceleration = m_constraints.maxAcceleration;
231 const Acceleration_t deceleration = -m_constraints.maxAcceleration;
232
233 Distance_t distToTarget = wpi::units::math::abs(target - position);
234
235 if (distToTarget < Distance_t{1e-6}) {
236 return 0_s;
237 }
238
239 Distance_t accelDist =
240 velocity * endAccel + 0.5 * acceleration * endAccel * endAccel;
241
242 Velocity_t decelVelocity;
243 if (endAccel > 0_s) {
244 decelVelocity = wpi::units::math::sqrt(wpi::units::math::abs(
245 velocity * velocity + 2 * acceleration * accelDist));
246 } else {
247 decelVelocity = velocity;
248 }
249
250 Distance_t fullVelocityDist = m_constraints.maxVelocity * endFullVelocity;
251 Distance_t decelDist;
252
253 if (accelDist > distToTarget) {
254 accelDist = distToTarget;
255 fullVelocityDist = Distance_t{0};
256 decelDist = Distance_t{0};
257 } else if (accelDist + fullVelocityDist > distToTarget) {
258 fullVelocityDist = distToTarget - accelDist;
259 decelDist = Distance_t{0};
260 } else {
261 decelDist = distToTarget - fullVelocityDist - accelDist;
262 }
263
264 wpi::units::second_t accelTime =
265 (-velocity + wpi::units::math::sqrt(wpi::units::math::abs(
266 velocity * velocity + 2 * acceleration * accelDist))) /
267 acceleration;
268
269 wpi::units::second_t decelTime =
270 (-decelVelocity +
271 wpi::units::math::sqrt(wpi::units::math::abs(
272 decelVelocity * decelVelocity + 2 * deceleration * decelDist))) /
273 deceleration;
274
275 wpi::units::second_t fullVelocityTime =
276 fullVelocityDist / m_constraints.maxVelocity;
277
278 return accelTime + fullVelocityTime + decelTime;
279 }
280
281 /**
282 * Returns the total time the profile takes to reach the goal.
283 *
284 * @return The total time the profile takes to reach the goal, or zero if no
285 * goal was set.
286 */
287 constexpr wpi::units::second_t TotalTime() const { return m_endDecel; }
288
289 /**
290 * Returns true if the profile has reached the goal.
291 *
292 * The profile has reached the goal if the time since the profile started has
293 * exceeded the profile's total time.
294 *
295 * @param t The time since the beginning of the profile.
296 * @return True if the profile has reached the goal.
297 */
298 constexpr bool IsFinished(wpi::units::second_t t) const {
299 return t >= TotalTime();
300 }
301
302 private:
303 /**
304 * Returns true if the profile inverted.
305 *
306 * The profile is inverted if goal position is less than the initial position.
307 *
308 * @param initial The initial state (usually the current state).
309 * @param goal The desired state when the profile is complete.
310 */
311 static constexpr bool ShouldFlipAcceleration(const State& initial,
312 const State& goal) {
313 return initial.position > goal.position;
314 }
315
316 // Flip the sign of the velocity and position if the profile is inverted
317 constexpr State Direct(const State& in) const {
318 State result = in;
319 result.position *= m_direction;
320 result.velocity *= m_direction;
321 return result;
322 }
323
324 // The direction of the profile, either 1 for forwards or -1 for inverted
325 int m_direction = 1;
326
327 Constraints m_constraints;
328 State m_current;
329
330 wpi::units::second_t m_endAccel = 0_s;
331 wpi::units::second_t m_endFullVelocity = 0_s;
332 wpi::units::second_t m_endDecel = 0_s;
333};
334
335} // namespace wpi::math
static void ReportUsage(std::string_view resource, std::string_view data)
Definition MathShared.hpp:61
Profile constraints.
Definition TrapezoidProfile.hpp:61
constexpr Constraints(Velocity_t maxVelocity, Acceleration_t maxAcceleration)
Constructs constraints for a Trapezoid Profile.
Definition TrapezoidProfile.hpp:84
Velocity_t maxVelocity
Maximum velocity.
Definition TrapezoidProfile.hpp:64
Acceleration_t maxAcceleration
Maximum acceleration.
Definition TrapezoidProfile.hpp:67
constexpr Constraints()
Default constructor.
Definition TrapezoidProfile.hpp:72
Profile state.
Definition TrapezoidProfile.hpp:100
Distance_t position
The position at this state.
Definition TrapezoidProfile.hpp:103
Velocity_t velocity
The velocity at this state.
Definition TrapezoidProfile.hpp:106
constexpr bool operator==(const State &) const =default
constexpr bool IsFinished(wpi::units::second_t t) const
Returns true if the profile has reached the goal.
Definition TrapezoidProfile.hpp:298
wpi::units::unit_t< Acceleration > Acceleration_t
Definition TrapezoidProfile.hpp:56
wpi::units::unit_t< Velocity > Velocity_t
Definition TrapezoidProfile.hpp:52
constexpr TrapezoidProfile(const TrapezoidProfile &)=default
constexpr TrapezoidProfile(Constraints constraints)
Constructs a TrapezoidProfile.
Definition TrapezoidProfile.hpp:116
wpi::units::compound_unit< Velocity, wpi::units::inverse< wpi::units::seconds > > Acceleration
Definition TrapezoidProfile.hpp:53
constexpr wpi::units::second_t TotalTime() const
Returns the total time the profile takes to reach the goal.
Definition TrapezoidProfile.hpp:287
constexpr State Calculate(wpi::units::second_t t, State current, State goal)
Calculates the position and velocity for the profile at a time t where the current state is at time t...
Definition TrapezoidProfile.hpp:134
constexpr TrapezoidProfile & operator=(const TrapezoidProfile &)=default
wpi::units::compound_unit< Distance, wpi::units::inverse< wpi::units::seconds > > Velocity
Definition TrapezoidProfile.hpp:49
constexpr wpi::units::second_t TimeLeftUntil(Distance_t target) const
Returns the time left until a target distance in the profile is reached.
Definition TrapezoidProfile.hpp:213
constexpr TrapezoidProfile & operator=(TrapezoidProfile &&)=default
constexpr TrapezoidProfile(TrapezoidProfile &&)=default
wpi::units::unit_t< Distance > Distance_t
Definition TrapezoidProfile.hpp:48
Definition LinearSystem.hpp:20