WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
MathUtil.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 <numbers>
8#include <type_traits>
9
10#include <gcem.hpp>
11
15#include "wpi/units/angle.hpp"
16#include "wpi/units/base.hpp"
17#include "wpi/units/length.hpp"
18#include "wpi/units/math.hpp"
19#include "wpi/units/time.hpp"
20#include "wpi/units/velocity.hpp"
22
23namespace wpi::math {
24
25/**
26 * Returns 0.0 if the given value is within the specified range around zero. The
27 * remaining range between the deadband and the maximum magnitude is scaled from
28 * 0.0 to the maximum magnitude.
29 *
30 * @param value Value to clip.
31 * @param deadband Range around zero.
32 * @param maxMagnitude The maximum magnitude of the input (defaults to 1). Can
33 * be infinite.
34 * @return The value after the deadband is applied.
35 */
36template <typename T>
37 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
38constexpr T ApplyDeadband(T value, T deadband, T maxMagnitude = T{1.0}) {
39 T magnitude;
40 if constexpr (std::is_arithmetic_v<T>) {
41 magnitude = gcem::abs(value);
42 } else {
43 magnitude = wpi::units::math::abs(value);
44 }
45
46 if (magnitude < deadband) {
47 return T{0.0};
48 }
49
50 if (value > T{0.0}) {
51 // Map deadband to 0 and map max to max with a linear relationship.
52 //
53 // y - y₁ = m(x - x₁)
54 // y - y₁ = (y₂ - y₁)/(x₂ - x₁) (x - x₁)
55 // y = (y₂ - y₁)/(x₂ - x₁) (x - x₁) + y₁
56 //
57 // (x₁, y₁) = (deadband, 0) and (x₂, y₂) = (max, max).
58 //
59 // x₁ = deadband
60 // y₁ = 0
61 // x₂ = max
62 // y₂ = max
63 // y = (max - 0)/(max - deadband) (x - deadband) + 0
64 // y = max/(max - deadband) (x - deadband)
65 //
66 // To handle high values of max, rewrite so that max only appears on the
67 // denominator.
68 //
69 // y = ((max - deadband) + deadband)/(max - deadband) (x - deadband)
70 // y = (1 + deadband/(max - deadband)) (x - deadband)
71 return (1.0 + deadband / (maxMagnitude - deadband)) * (value - deadband);
72 } else {
73 // Map -deadband to 0 and map -max to -max with a linear relationship.
74 //
75 // y - y₁ = m(x - x₁)
76 // y - y₁ = (y₂ - y₁)/(x₂ - x₁) (x - x₁)
77 // y = (y₂ - y₁)/(x₂ - x₁) (x - x₁) + y₁
78 //
79 // (x₁, y₁) = (-deadband, 0) and (x₂, y₂) = (-max, -max).
80 //
81 // x₁ = -deadband
82 // y₁ = 0
83 // x₂ = -max
84 // y₂ = -max
85 // y = (-max - 0)/(-max + deadband) (x + deadband) + 0
86 // y = max/(max - deadband) (x + deadband)
87 //
88 // To handle high values of max, rewrite so that max only appears on the
89 // denominator.
90 //
91 // y = ((max - deadband) + deadband)/(max - deadband) (x + deadband)
92 // y = (1 + deadband/(max - deadband)) (x + deadband)
93 return (1.0 + deadband / (maxMagnitude - deadband)) * (value + deadband);
94 }
95}
96
97/**
98 * Returns a zero vector if the given vector is within the specified
99 * distance from the origin. The remaining distance between the deadband and the
100 * maximum distance is scaled from the origin to the maximum distance.
101 *
102 * @param value Value to clip.
103 * @param deadband Distance from origin.
104 * @param maxMagnitude The maximum distance from the origin of the input
105 * (defaults to 1). Can be infinite.
106 * @return The value after the deadband is applied.
107 */
108template <typename T, int N>
109 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
110Eigen::Vector<T, N> ApplyDeadband(const Eigen::Vector<T, N>& value, T deadband,
111 T maxMagnitude = T{1.0}) {
112 if constexpr (std::is_arithmetic_v<T>) {
113 if (value.norm() < T{1e-9}) {
114 return Eigen::Vector<T, N>::Zero();
115 }
116 return value.normalized() *
117 ApplyDeadband(value.norm(), deadband, maxMagnitude);
118 } else {
119 const Eigen::Vector<double, N> asDouble = value.template cast<double>();
120 const Eigen::Vector<double, N> processed =
121 ApplyDeadband(asDouble, deadband.value(), maxMagnitude.value());
122 return processed.template cast<T>();
123 }
124}
125
126/**
127 * Raises the input to the power of the given exponent while preserving its
128 * sign.
129 *
130 * The function normalizes the input value to the range [0, 1] based on the
131 * maximum magnitude so that the output stays in the range.
132 *
133 * This is useful for applying smoother or more aggressive control response
134 * curves (e.g. joystick input shaping).
135 *
136 * @param value The input value to transform.
137 * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared
138 * curve). Must be positive.
139 * @param maxMagnitude The maximum expected absolute value of input (defaults to
140 * 1). Must be positive.
141 * @return The transformed value with the same sign and scaled to the input
142 * range.
143 */
144template <typename T>
145 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
146constexpr T CopyDirectionPow(T value, double exponent,
147 T maxMagnitude = T{1.0}) {
148 if constexpr (std::is_arithmetic_v<T>) {
149 return gcem::copysign(
150 gcem::pow(gcem::abs(value) / maxMagnitude, exponent) * maxMagnitude,
151 value);
152 } else {
153 return wpi::units::math::copysign(
154 gcem::pow((wpi::units::math::abs(value) / maxMagnitude).value(),
155 exponent) *
156 maxMagnitude,
157 value);
158 }
159}
160
161/**
162 * Raises the norm of the input to the power of the given exponent while
163 * preserving its direction.
164 *
165 * The function normalizes the input value to the range [0, 1] based on the
166 * maximum magnitude so that the output stays in the range.
167 *
168 * This is useful for applying smoother or more aggressive control response
169 * curves (e.g. joystick input shaping).
170 *
171 * @param value The input vector to transform.
172 * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared
173 * curve). Must be positive.
174 * @param maxMagnitude The maximum expected distance from origin of input
175 * (defaults to 1). Must be positive.
176 * @return The transformed value with the same direction and norm scaled to
177 * the input range.
178 */
179template <typename T, int N>
180 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
181Eigen::Vector<T, N> CopyDirectionPow(const Eigen::Vector<T, N>& value,
182 double exponent, T maxMagnitude = T{1.0}) {
183 if constexpr (std::is_arithmetic_v<T>) {
184 if (value.norm() < T{1e-9}) {
185 return Eigen::Vector<T, N>::Zero();
186 }
187 return value.normalized() *
188 CopyDirectionPow(value.norm(), exponent, maxMagnitude);
189 } else {
190 const Eigen::Vector<double, N> asDouble = value.template cast<double>();
191 const Eigen::Vector<double, N> processed =
192 CopyDirectionPow(asDouble, exponent, maxMagnitude.value());
193 return processed.template cast<T>();
194 }
195}
196
197/**
198 * Returns modulus of input.
199 *
200 * @param input Input value to wrap.
201 * @param minimumInput The minimum value expected from the input.
202 * @param maximumInput The maximum value expected from the input.
203 */
204template <typename T>
205constexpr T InputModulus(T input, T minimumInput, T maximumInput) {
206 T modulus = maximumInput - minimumInput;
207
208 // Wrap input if it's above the maximum input
209 int numMax = (input - minimumInput) / modulus;
210 input -= numMax * modulus;
211
212 // Wrap input if it's below the minimum input
213 int numMin = (input - maximumInput) / modulus;
214 input -= numMin * modulus;
215
216 return input;
217}
218
219/**
220 * Checks if the given value matches an expected value within a certain
221 * tolerance.
222 *
223 * @param expected The expected value
224 * @param actual The actual value
225 * @param tolerance The allowed difference between the actual and the expected
226 * value
227 * @return Whether or not the actual value is within the allowed tolerance
228 */
229template <typename T>
230 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
231constexpr bool IsNear(T expected, T actual, T tolerance) {
232 if constexpr (std::is_arithmetic_v<T>) {
233 return std::abs(expected - actual) < tolerance;
234 } else {
235 return wpi::units::math::abs(expected - actual) < tolerance;
236 }
237}
238
239/**
240 * Checks if the given value matches an expected value within a certain
241 * tolerance. Supports continuous input for cases like absolute encoders.
242 *
243 * Continuous input means that the min and max value are considered to be the
244 * same point, and tolerances can be checked across them. A common example
245 * would be for absolute encoders: calling isNear(2, 359, 5, 0, 360) returns
246 * true because 359 is 1 away from 360 (which is treated as the same as 0) and
247 * 2 is 2 away from 0, adding up to an error of 3 degrees, which is within the
248 * given tolerance of 5.
249 *
250 * @param expected The expected value
251 * @param actual The actual value
252 * @param tolerance The allowed difference between the actual and the expected
253 * value
254 * @param min Smallest value before wrapping around to the largest value
255 * @param max Largest value before wrapping around to the smallest value
256 * @return Whether or not the actual value is within the allowed tolerance
257 */
258template <typename T>
259 requires std::is_arithmetic_v<T> || wpi::units::traits::is_unit_t_v<T>
260constexpr bool IsNear(T expected, T actual, T tolerance, T min, T max) {
261 T errorBound = (max - min) / 2.0;
262 T error =
263 wpi::math::InputModulus<T>(expected - actual, -errorBound, errorBound);
264
265 if constexpr (std::is_arithmetic_v<T>) {
266 return std::abs(error) < tolerance;
267 } else {
268 return wpi::units::math::abs(error) < tolerance;
269 }
270}
271
272/**
273 * Wraps an angle to the range -π to π radians (-180 to 180 degrees).
274 *
275 * @param angle Angle to wrap.
276 */
278constexpr wpi::units::radian_t AngleModulus(wpi::units::radian_t angle) {
280 angle, wpi::units::radian_t{-std::numbers::pi},
281 wpi::units::radian_t{std::numbers::pi});
282}
283
284// floorDiv and floorMod algorithms taken from Java
285
286/**
287 * Returns the largest (closest to positive infinity)
288 * {@code int} value that is less than or equal to the algebraic quotient.
289 *
290 * @param x the dividend
291 * @param y the divisor
292 * @return the largest (closest to positive infinity)
293 * {@code int} value that is less than or equal to the algebraic quotient.
294 */
295constexpr std::signed_integral auto FloorDiv(std::signed_integral auto x,
296 std::signed_integral auto y) {
297 auto quot = x / y;
298 auto rem = x % y;
299 // if the signs are different and modulo not zero, round down
300 if ((x < 0) != (y < 0) && rem != 0) {
301 --quot;
302 }
303 return quot;
304}
305
306/**
307 * Returns the floor modulus of the {@code int} arguments.
308 * <p>
309 * The floor modulus is {@code r = x - (floorDiv(x, y) * y)},
310 * has the same sign as the divisor {@code y} or is zero, and
311 * is in the range of {@code -std::abs(y) < r < +std::abs(y)}.
312 *
313 * @param x the dividend
314 * @param y the divisor
315 * @return the floor modulus {@code x - (floorDiv(x, y) * y)}
316 */
317constexpr std::signed_integral auto FloorMod(std::signed_integral auto x,
318 std::signed_integral auto y) {
319 return x - FloorDiv(x, y) * y;
320}
321
322/**
323 * Limits translation velocity.
324 *
325 * @param current Translation at current timestep.
326 * @param next Translation at next timestep.
327 * @param dt Timestep duration.
328 * @param maxVelocity Maximum translation velocity.
329 * @return Returns the next Translation2d limited to maxVelocity
330 */
332 const Translation2d& current, const Translation2d& next,
333 wpi::units::second_t dt, wpi::units::meters_per_second_t maxVelocity) {
334 if (maxVelocity < 0_mps) {
336 "maxVelocity must be a non-negative number, got {}!", maxVelocity);
337 return next;
338 }
339 Translation2d diff = next - current;
340 wpi::units::meter_t dist = diff.Norm();
341 if (dist < 1e-9_m) {
342 return next;
343 }
344 if (dist > maxVelocity * dt) {
345 // Move maximum allowed amount in direction of the difference
346 // NOLINTNEXTLINE(bugprone-integer-division)
347 return current + diff * (maxVelocity * dt / dist);
348 }
349 return next;
350}
351
352/**
353 * Limits translation velocity.
354 *
355 * @param current Translation at current timestep.
356 * @param next Translation at next timestep.
357 * @param dt Timestep duration.
358 * @param maxVelocity Maximum translation velocity.
359 * @return Returns the next Translation3d limited to maxVelocity
360 */
362 const Translation3d& current, const Translation3d& next,
363 wpi::units::second_t dt, wpi::units::meters_per_second_t maxVelocity) {
364 if (maxVelocity < 0_mps) {
366 "maxVelocity must be a non-negative number, got {}!", maxVelocity);
367 return next;
368 }
369 Translation3d diff = next - current;
370 wpi::units::meter_t dist = diff.Norm();
371 if (dist < 1e-9_m) {
372 return next;
373 }
374 if (dist > maxVelocity * dt) {
375 // Move maximum allowed amount in direction of the difference
376 // NOLINTNEXTLINE(bugprone-integer-division)
377 return current + diff * (maxVelocity * dt / dist);
378 }
379 return next;
380}
381
382} // namespace wpi::math
#define WPILIB_DLLEXPORT
Definition SymbolExports.hpp:36
static void ReportError(const S &format, Args &&... args)
Definition MathShared.hpp:48
Represents a translation in 2D space.
Definition Translation2d.hpp:30
constexpr wpi::units::meter_t Norm() const
Returns the norm, or distance from the origin to the translation.
Definition Translation2d.hpp:125
Represents a translation in 3D space.
Definition Translation3d.hpp:31
constexpr wpi::units::meter_t Norm() const
Returns the norm, or distance from the origin to the translation.
Definition Translation3d.hpp:155
constexpr T abs(const T x) noexcept
Compile-time absolute value function.
Definition abs.hpp:40
constexpr T1 copysign(const T1 x, const T2 y) noexcept
Compile-time copy sign function.
Definition copysign.hpp:41
constexpr common_t< T1, T2 > pow(const T1 base, const T2 exp_term) noexcept
Compile-time power function.
Definition pow.hpp:82
Definition LinearSystem.hpp:20
constexpr Translation2d SlewRateLimit(const Translation2d &current, const Translation2d &next, wpi::units::second_t dt, wpi::units::meters_per_second_t maxVelocity)
Limits translation velocity.
Definition MathUtil.hpp:331
constexpr std::signed_integral auto FloorMod(std::signed_integral auto x, std::signed_integral auto y)
Returns the floor modulus of the int arguments.
Definition MathUtil.hpp:317
constexpr T ApplyDeadband(T value, T deadband, T maxMagnitude=T{1.0})
Returns 0.0 if the given value is within the specified range around zero.
Definition MathUtil.hpp:38
WPILIB_DLLEXPORT constexpr wpi::units::radian_t AngleModulus(wpi::units::radian_t angle)
Wraps an angle to the range -π to π radians (-180 to 180 degrees).
Definition MathUtil.hpp:278
constexpr T InputModulus(T input, T minimumInput, T maximumInput)
Returns modulus of input.
Definition MathUtil.hpp:205
constexpr bool IsNear(T expected, T actual, T tolerance)
Checks if the given value matches an expected value within a certain tolerance.
Definition MathUtil.hpp:231
constexpr std::signed_integral auto FloorDiv(std::signed_integral auto x, std::signed_integral auto y)
Returns the largest (closest to positive infinity) int value that is less than or equal to the algebr...
Definition MathUtil.hpp:295
constexpr T CopyDirectionPow(T value, double exponent, T maxMagnitude=T{1.0})
Raises the input to the power of the given exponent while preserving its sign.
Definition MathUtil.hpp:146