WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
ExponentialProfile.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 "wpi/units/math.hpp"
8#include "wpi/units/time.hpp"
9
10namespace wpi::math {
11
12/**
13 * A Exponential-shaped velocity profile.
14 *
15 * While this class can be used for a profiled movement from start to finish,
16 * the intended usage is to filter a reference's dynamics based on
17 * ExponentialProfile velocity constraints. To compute the reference obeying
18 * this constraint, do the following.
19 *
20 * Initialization:
21 * @code{.cpp}
22 * ExponentialProfile::Constraints constraints{kMaxV, kV, kA};
23 * State previousProfiledReference = {initialReference, 0_mps};
24 * @endcode
25 *
26 * Run on update:
27 * @code{.cpp}
28 * previousProfiledReference = profile.Calculate(timeSincePreviousUpdate,
29 * previousProfiledReference, unprofiledReference);
30 * @endcode
31 *
32 * where `unprofiledReference` is free to change between calls. Note that when
33 * the unprofiled reference is within the constraints, `Calculate()` returns the
34 * unprofiled reference unchanged.
35 *
36 * Otherwise, a timer can be started to provide monotonic values for
37 * `Calculate()` and to determine when the profile has completed via
38 * `IsFinished()`.
39 */
40template <class Distance, class Input>
42 public:
43 using Distance_t = wpi::units::unit_t<Distance>;
44 using Velocity =
45 wpi::units::compound_unit<Distance,
46 wpi::units::inverse<wpi::units::seconds>>;
47 using Velocity_t = wpi::units::unit_t<Velocity>;
49 wpi::units::compound_unit<Velocity,
50 wpi::units::inverse<wpi::units::seconds>>;
51 using Input_t = wpi::units::unit_t<Input>;
52 using A_t = wpi::units::unit_t<wpi::units::inverse<wpi::units::seconds>>;
53 using B_t = wpi::units::unit_t<
54 wpi::units::compound_unit<Acceleration, wpi::units::inverse<Input>>>;
55 using KV = wpi::units::compound_unit<Input, wpi::units::inverse<Velocity>>;
56 using kV_t = wpi::units::unit_t<KV>;
57 using KA =
58 wpi::units::compound_unit<Input, wpi::units::inverse<Acceleration>>;
59 using kA_t = wpi::units::unit_t<KA>;
60
61 /**
62 * Profile timing.
63 */
65 public:
66 /// Profile inflection time.
67 wpi::units::second_t inflectionTime;
68
69 /// Total profile time.
70 wpi::units::second_t totalTime;
71
72 /**
73 * Decides if the profile is finished by time t.
74 *
75 * @param t The time since the beginning of the profile.
76 * @return if the profile is finished at time t.
77 */
78 constexpr bool IsFinished(const wpi::units::second_t& t) const {
79 return t >= totalTime;
80 }
81 };
82
83 /**
84 * Profile constraints.
85 */
87 public:
88 /**
89 * Constructs constraints for an ExponentialProfile.
90 *
91 * @param maxInput maximum unsigned input voltage
92 * @param A The State-Space 1x1 system matrix.
93 * @param B The State-Space 1x1 input matrix.
94 */
96 : maxInput{maxInput}, A{A}, B{B} {}
97
98 /**
99 * Constructs constraints for an ExponentialProfile from characteristics.
100 *
101 * @param maxInput maximum unsigned input voltage
102 * @param kV The velocity gain.
103 * @param kA The acceleration gain.
104 */
106 : maxInput{maxInput}, A{-kV / kA}, B{1 / kA} {}
107
108 /**
109 * Computes the max achievable velocity for an Exponential Profile.
110 *
111 * @return The steady-state velocity achieved by this profile.
112 */
113 constexpr Velocity_t MaxVelocity() const { return -maxInput * B / A; }
114
115 /// Maximum unsigned input voltage.
117
118 /// The State-Space 1x1 system matrix.
119 A_t A{0};
120
121 /// The State-Space 1x1 input matrix.
122 B_t B{0};
123 };
124
125 /** Profile state. */
126 class State {
127 public:
128 /// The position at this state.
130
131 /// The velocity at this state.
133
134 constexpr bool operator==(const State&) const = default;
135 };
136
137 /**
138 * Constructs a ExponentialProfile.
139 *
140 * @param constraints The constraints on the profile, like maximum input.
141 */
142 constexpr explicit ExponentialProfile(Constraints constraints)
143 : m_constraints(constraints) {}
144
145 constexpr ExponentialProfile(const ExponentialProfile&) = default;
146 constexpr ExponentialProfile& operator=(const ExponentialProfile&) = default;
149
150 /**
151 * Calculates the position and velocity for the profile at a time t where the
152 * current state is at time t = 0.
153 *
154 * @param t How long to advance from the current state toward the desired
155 * state.
156 * @param current The current state.
157 * @param goal The desired state when the profile is complete.
158 * @return The position and velocity of the profile at time t.
159 */
160 constexpr State Calculate(const wpi::units::second_t& t, const State& current,
161 const State& goal) const {
162 auto direction = ShouldFlipInput(current, goal) ? -1 : 1;
163 auto u = direction * m_constraints.maxInput;
164
165 auto inflectionPoint = CalculateInflectionPoint(current, goal, u);
166 auto timing = CalculateProfileTiming(current, inflectionPoint, goal, u);
167
168 if (t < 0_s) {
169 return current;
170 } else if (t < timing.inflectionTime) {
171 return {ComputeDistanceFromTime(t, u, current),
172 ComputeVelocityFromTime(t, u, current)};
173 } else if (t < timing.totalTime) {
174 return {ComputeDistanceFromTime(t - timing.totalTime, -u, goal),
175 ComputeVelocityFromTime(t - timing.totalTime, -u, goal)};
176 } else {
177 return goal;
178 }
179 }
180
181 /**
182 * Calculates the point after which the fastest way to reach the goal state is
183 * to apply input in the opposite direction.
184 *
185 * @param current The current state.
186 * @param goal The desired state when the profile is complete.
187 * @return The position and velocity of the profile at the inflection point.
188 */
189 constexpr State CalculateInflectionPoint(const State& current,
190 const State& goal) const {
191 auto direction = ShouldFlipInput(current, goal) ? -1 : 1;
192 auto u = direction * m_constraints.maxInput;
193
194 return CalculateInflectionPoint(current, goal, u);
195 }
196
197 /**
198 * Calculates the time it will take for this profile to reach the goal state.
199 *
200 * @param current The current state.
201 * @param goal The desired state when the profile is complete.
202 * @return The total duration of this profile.
203 */
204 constexpr wpi::units::second_t TimeLeftUntil(const State& current,
205 const State& goal) const {
206 auto timing = CalculateProfileTiming(current, goal);
207
208 return timing.totalTime;
209 }
210
211 /**
212 * Calculates the time it will take for this profile to reach the inflection
213 * point, and the time it will take for this profile to reach the goal state.
214 *
215 * @param current The current state.
216 * @param goal The desired state when the profile is complete.
217 * @return The timing information for this profile.
218 */
220 const State& goal) const {
221 auto direction = ShouldFlipInput(current, goal) ? -1 : 1;
222 auto u = direction * m_constraints.maxInput;
223
224 auto inflectionPoint = CalculateInflectionPoint(current, goal, u);
225 return CalculateProfileTiming(current, inflectionPoint, goal, u);
226 }
227
228 private:
229 /**
230 * Calculates the point after which the fastest way to reach the goal state is
231 * to apply input in the opposite direction.
232 *
233 * @param current The current state.
234 * @param goal The desired state when the profile is complete.
235 * @param input The signed input applied to this profile from the current
236 * state.
237 * @return The position and velocity of the profile at the inflection point.
238 */
239 constexpr State CalculateInflectionPoint(const State& current,
240 const State& goal,
241 const Input_t& input) const {
242 auto u = input;
243
244 if (current == goal) {
245 return current;
246 }
247
248 auto inflectionVelocity = SolveForInflectionVelocity(u, current, goal);
249 auto inflectionPosition =
250 ComputeDistanceFromVelocity(inflectionVelocity, -u, goal);
251
252 return {inflectionPosition, inflectionVelocity};
253 }
254
255 /**
256 * Calculates the time it will take for this profile to reach the inflection
257 * point, and the time it will take for this profile to reach the goal state.
258 *
259 * @param current The current state.
260 * @param inflectionPoint The inflection point of this profile.
261 * @param goal The desired state when the profile is complete.
262 * @param input The signed input applied to this profile from the current
263 * state.
264 * @return The timing information for this profile.
265 */
266 constexpr ProfileTiming CalculateProfileTiming(const State& current,
267 const State& inflectionPoint,
268 const State& goal,
269 const Input_t& input) const {
270 auto u = input;
271 auto u_dir = wpi::units::math::abs(u) / u;
272
273 wpi::units::second_t inflectionT_forward;
274
275 // We need to handle 5 cases here:
276 //
277 // - Approaching -maxVelocity from below
278 // - Approaching -maxVelocity from above
279 // - Approaching maxVelocity from below
280 // - Approaching maxVelocity from above
281 // - At +-maxVelocity
282 //
283 // For cases 1 and 3, we want to subtract epsilon from the inflection point
284 // velocity For cases 2 and 4, we want to add epsilon to the inflection
285 // point velocity. For case 5, we have reached inflection point velocity.
286 auto epsilon = Velocity_t(1e-9);
287 if (wpi::units::math::abs(u_dir * m_constraints.MaxVelocity() -
288 inflectionPoint.velocity) < epsilon) {
289 auto solvableV = inflectionPoint.velocity;
290 wpi::units::second_t t_to_solvable_v;
291 Distance_t x_at_solvable_v;
292 if (wpi::units::math::abs(current.velocity - inflectionPoint.velocity) <
293 epsilon) {
294 t_to_solvable_v = 0_s;
295 x_at_solvable_v = current.position;
296 } else {
297 if (wpi::units::math::abs(current.velocity) >
298 m_constraints.MaxVelocity()) {
299 solvableV += u_dir * epsilon;
300 } else {
301 solvableV -= u_dir * epsilon;
302 }
303
304 t_to_solvable_v =
305 ComputeTimeFromVelocity(solvableV, u, current.velocity);
306 x_at_solvable_v = ComputeDistanceFromVelocity(solvableV, u, current);
307 }
308
309 inflectionT_forward =
310 t_to_solvable_v + u_dir *
311 (inflectionPoint.position - x_at_solvable_v) /
312 m_constraints.MaxVelocity();
313 } else {
314 inflectionT_forward = ComputeTimeFromVelocity(inflectionPoint.velocity, u,
315 current.velocity);
316 }
317
318 auto inflectionT_backward =
319 ComputeTimeFromVelocity(inflectionPoint.velocity, -u, goal.velocity);
320
321 return {inflectionT_forward, inflectionT_forward - inflectionT_backward};
322 }
323
324 /**
325 * Calculates the position reached after t seconds when applying an input from
326 * the initial state.
327 *
328 * @param t The time since the initial state.
329 * @param input The signed input applied to this profile from the initial
330 * state.
331 * @param initial The initial state.
332 * @return The distance travelled by this profile.
333 */
334 constexpr Distance_t ComputeDistanceFromTime(const wpi::units::second_t& time,
335 const Input_t& input,
336 const State& initial) const {
337 auto A = m_constraints.A;
338 auto B = m_constraints.B;
339 auto u = input;
340
341 return initial.position +
342 (-B * u * time + (initial.velocity + B * u / A) *
343 (wpi::units::math::exp(A * time) - 1)) /
344 A;
345 }
346
347 /**
348 * Calculates the velocity reached after t seconds when applying an input from
349 * the initial state.
350 *
351 * @param t The time since the initial state.
352 * @param input The signed input applied to this profile from the initial
353 * state.
354 * @param initial The initial state.
355 * @return The distance travelled by this profile.
356 */
357 constexpr Velocity_t ComputeVelocityFromTime(const wpi::units::second_t& time,
358 const Input_t& input,
359 const State& initial) const {
360 auto A = m_constraints.A;
361 auto B = m_constraints.B;
362 auto u = input;
363
364 return (initial.velocity + B * u / A) * wpi::units::math::exp(A * time) -
365 B * u / A;
366 }
367
368 /**
369 * Calculates the time required to reach a specified velocity given the
370 * initial velocity.
371 *
372 * @param velocity The goal velocity.
373 * @param input The signed input applied to this profile from the initial
374 * state.
375 * @param initial The initial velocity.
376 * @return The time required to reach the goal velocity.
377 */
378 constexpr wpi::units::second_t ComputeTimeFromVelocity(
379 const Velocity_t& velocity, const Input_t& input,
380 const Velocity_t& initial) const {
381 auto A = m_constraints.A;
382 auto B = m_constraints.B;
383 auto u = input;
384
385 return wpi::units::math::log((A * velocity + B * u) /
386 (A * initial + B * u)) /
387 A;
388 }
389
390 /**
391 * Calculates the distance reached at the same time as the given velocity when
392 * applying the given input from the initial state.
393 *
394 * @param velocity The velocity reached by this profile
395 * @param input The signed input applied to this profile from the initial
396 * state.
397 * @param initial The initial state.
398 * @return The distance reached when the given velocity is reached.
399 */
400 constexpr Distance_t ComputeDistanceFromVelocity(const Velocity_t& velocity,
401 const Input_t& input,
402 const State& initial) const {
403 auto A = m_constraints.A;
404 auto B = m_constraints.B;
405 auto u = input;
406
407 return initial.position + (velocity - initial.velocity) / A -
408 B * u / (A * A) *
409 wpi::units::math::log((A * velocity + B * u) /
410 (A * initial.velocity + B * u));
411 }
412
413 /**
414 * Calculates the velocity at which input should be reversed in order to reach
415 * the goal state from the current state.
416 *
417 * @param input The signed input applied to this profile from the current
418 * state.
419 * @param current The current state.
420 * @param goal The goal state.
421 * @return The inflection velocity.
422 */
423 constexpr Velocity_t SolveForInflectionVelocity(const Input_t& input,
424 const State& current,
425 const State& goal) const {
426 auto A = m_constraints.A;
427 auto B = m_constraints.B;
428 auto u = input;
429
430 auto u_dir = u / wpi::units::math::abs(u);
431
432 auto position_delta = goal.position - current.position;
433 auto velocity_delta = goal.velocity - current.velocity;
434
435 auto scalar = (A * current.velocity + B * u) * (A * goal.velocity - B * u);
436 auto power = -A / B / u * (A * position_delta - velocity_delta);
437
438 auto a = -A * A;
439 auto c = B * B * u * u + scalar * wpi::units::math::exp(power);
440
441 if (-1e-9 < c.value() && c.value() < 0) {
442 // numeric instability - the heuristic gets it right but c is around
443 // -1e-13
444 return Velocity_t(0);
445 }
446
447 return u_dir * wpi::units::math::sqrt(-c / a);
448 }
449
450 /**
451 * Returns true if the profile should be inverted.
452 *
453 * The profile is inverted if we should first apply negative input in order to
454 * reach the goal state.
455 *
456 * @param current The initial state (usually the current state).
457 * @param goal The desired state when the profile is complete.
458 */
459 constexpr bool ShouldFlipInput(const State& current,
460 const State& goal) const {
461 auto u = m_constraints.maxInput;
462
463 auto v0 = current.velocity;
464 auto xf = goal.position;
465 auto vf = goal.velocity;
466
467 auto x_forward = ComputeDistanceFromVelocity(vf, u, current);
468 auto x_reverse = ComputeDistanceFromVelocity(vf, -u, current);
469
470 if (v0 >= m_constraints.MaxVelocity()) {
471 return xf < x_reverse;
472 }
473
474 if (v0 <= -m_constraints.MaxVelocity()) {
475 return xf < x_forward;
476 }
477
478 auto a = v0 >= Velocity_t(0);
479 auto b = vf >= Velocity_t(0);
480 auto c = xf >= x_forward;
481 auto d = xf >= x_reverse;
482
483 return (a && !d) || (b && !c) || (!c && !d);
484 }
485
486 Constraints m_constraints;
487};
488
489} // namespace wpi::math
Profile constraints.
Definition ExponentialProfile.hpp:86
Input_t maxInput
Maximum unsigned input voltage.
Definition ExponentialProfile.hpp:116
constexpr Constraints(Input_t maxInput, kV_t kV, kA_t kA)
Constructs constraints for an ExponentialProfile from characteristics.
Definition ExponentialProfile.hpp:105
B_t B
The State-Space 1x1 input matrix.
Definition ExponentialProfile.hpp:122
A_t A
The State-Space 1x1 system matrix.
Definition ExponentialProfile.hpp:119
constexpr Velocity_t MaxVelocity() const
Computes the max achievable velocity for an Exponential Profile.
Definition ExponentialProfile.hpp:113
constexpr Constraints(Input_t maxInput, A_t A, B_t B)
Constructs constraints for an ExponentialProfile.
Definition ExponentialProfile.hpp:95
Profile timing.
Definition ExponentialProfile.hpp:64
wpi::units::second_t totalTime
Total profile time.
Definition ExponentialProfile.hpp:70
constexpr bool IsFinished(const wpi::units::second_t &t) const
Decides if the profile is finished by time t.
Definition ExponentialProfile.hpp:78
wpi::units::second_t inflectionTime
Profile inflection time.
Definition ExponentialProfile.hpp:67
Profile state.
Definition ExponentialProfile.hpp:126
Velocity_t velocity
The velocity at this state.
Definition ExponentialProfile.hpp:132
constexpr bool operator==(const State &) const =default
Distance_t position
The position at this state.
Definition ExponentialProfile.hpp:129
constexpr ExponentialProfile(const ExponentialProfile &)=default
wpi::units::compound_unit< Input, wpi::units::inverse< Acceleration > > KA
Definition ExponentialProfile.hpp:57
wpi::units::unit_t< Input > Input_t
Definition ExponentialProfile.hpp:51
wpi::units::compound_unit< Distance, wpi::units::inverse< wpi::units::seconds > > Velocity
Definition ExponentialProfile.hpp:44
constexpr ExponentialProfile & operator=(const ExponentialProfile &)=default
wpi::units::unit_t< KV > kV_t
Definition ExponentialProfile.hpp:56
constexpr ProfileTiming CalculateProfileTiming(const State &current, const State &goal) const
Calculates the time it will take for this profile to reach the inflection point, and the time it will...
Definition ExponentialProfile.hpp:219
constexpr wpi::units::second_t TimeLeftUntil(const State &current, const State &goal) const
Calculates the time it will take for this profile to reach the goal state.
Definition ExponentialProfile.hpp:204
constexpr ExponentialProfile & operator=(ExponentialProfile &&)=default
wpi::units::unit_t< Velocity > Velocity_t
Definition ExponentialProfile.hpp:47
wpi::units::unit_t< KA > kA_t
Definition ExponentialProfile.hpp:59
wpi::units::unit_t< wpi::units::inverse< wpi::units::seconds > > A_t
Definition ExponentialProfile.hpp:52
constexpr ExponentialProfile(Constraints constraints)
Constructs a ExponentialProfile.
Definition ExponentialProfile.hpp:142
constexpr ExponentialProfile(ExponentialProfile &&)=default
wpi::units::compound_unit< Input, wpi::units::inverse< Velocity > > KV
Definition ExponentialProfile.hpp:55
wpi::units::unit_t< wpi::units::compound_unit< Acceleration, wpi::units::inverse< Input > > > B_t
Definition ExponentialProfile.hpp:53
wpi::units::compound_unit< Velocity, wpi::units::inverse< wpi::units::seconds > > Acceleration
Definition ExponentialProfile.hpp:48
wpi::units::unit_t< Distance > Distance_t
Definition ExponentialProfile.hpp:43
constexpr State CalculateInflectionPoint(const State &current, const State &goal) const
Calculates the point after which the fastest way to reach the goal state is to apply input in the opp...
Definition ExponentialProfile.hpp:189
constexpr State Calculate(const wpi::units::second_t &t, const State &current, const State &goal) const
Calculates the position and velocity for the profile at a time t where the current state is at time t...
Definition ExponentialProfile.hpp:160
Definition LinearSystem.hpp:20