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