WPILibC++ 2027.0.0-alpha-3
Loading...
Searching...
No Matches
MathUtil.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 <numbers>
8#include <type_traits>
9
10#include <gcem.hpp>
11#include <wpi/SymbolExports.h>
12
15#include "units/angle.h"
16#include "units/base.h"
17#include "units/length.h"
18#include "units/math.h"
19#include "units/time.h"
20#include "units/velocity.h"
21#include "wpimath/MathShared.h"
22
23namespace frc {
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> || 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 = 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> || 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> || 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 units::math::copysign(
154 gcem::pow((units::math::abs(value) / maxMagnitude).value(), exponent) *
155 maxMagnitude,
156 value);
157 }
158}
159
160/**
161 * Raises the norm of the input to the power of the given exponent while
162 * preserving its direction.
163 *
164 * The function normalizes the input value to the range [0, 1] based on the
165 * maximum magnitude so that the output stays in the range.
166 *
167 * This is useful for applying smoother or more aggressive control response
168 * curves (e.g. joystick input shaping).
169 *
170 * @param value The input vector to transform.
171 * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared
172 * curve). Must be positive.
173 * @param maxMagnitude The maximum expected distance from origin of input
174 * (defaults to 1). Must be positive.
175 * @return The transformed value with the same direction and norm scaled to
176 * the input range.
177 */
178template <typename T, int N>
179 requires std::is_arithmetic_v<T> || units::traits::is_unit_t_v<T>
180Eigen::Vector<T, N> CopyDirectionPow(const Eigen::Vector<T, N>& value,
181 double exponent, T maxMagnitude = T{1.0}) {
182 if constexpr (std::is_arithmetic_v<T>) {
183 if (value.norm() < T{1e-9}) {
184 return Eigen::Vector<T, N>::Zero();
185 }
186 return value.normalized() *
187 CopyDirectionPow(value.norm(), exponent, maxMagnitude);
188 } else {
189 const Eigen::Vector<double, N> asDouble = value.template cast<double>();
190 const Eigen::Vector<double, N> processed =
191 CopyDirectionPow(asDouble, exponent, maxMagnitude.value());
192 return processed.template cast<T>();
193 }
194}
195
196/**
197 * Returns modulus of input.
198 *
199 * @param input Input value to wrap.
200 * @param minimumInput The minimum value expected from the input.
201 * @param maximumInput The maximum value expected from the input.
202 */
203template <typename T>
204constexpr T InputModulus(T input, T minimumInput, T maximumInput) {
205 T modulus = maximumInput - minimumInput;
206
207 // Wrap input if it's above the maximum input
208 int numMax = (input - minimumInput) / modulus;
209 input -= numMax * modulus;
210
211 // Wrap input if it's below the minimum input
212 int numMin = (input - maximumInput) / modulus;
213 input -= numMin * modulus;
214
215 return input;
216}
217
218/**
219 * Checks if the given value matches an expected value within a certain
220 * tolerance.
221 *
222 * @param expected The expected value
223 * @param actual The actual value
224 * @param tolerance The allowed difference between the actual and the expected
225 * value
226 * @return Whether or not the actual value is within the allowed tolerance
227 */
228template <typename T>
229 requires std::is_arithmetic_v<T> || units::traits::is_unit_t_v<T>
230constexpr bool IsNear(T expected, T actual, T tolerance) {
231 if constexpr (std::is_arithmetic_v<T>) {
232 return std::abs(expected - actual) < tolerance;
233 } else {
234 return units::math::abs(expected - actual) < tolerance;
235 }
236}
237
238/**
239 * Checks if the given value matches an expected value within a certain
240 * tolerance. Supports continuous input for cases like absolute encoders.
241 *
242 * Continuous input means that the min and max value are considered to be the
243 * same point, and tolerances can be checked across them. A common example
244 * would be for absolute encoders: calling isNear(2, 359, 5, 0, 360) returns
245 * true because 359 is 1 away from 360 (which is treated as the same as 0) and
246 * 2 is 2 away from 0, adding up to an error of 3 degrees, which is within the
247 * given tolerance of 5.
248 *
249 * @param expected The expected value
250 * @param actual The actual value
251 * @param tolerance The allowed difference between the actual and the expected
252 * value
253 * @param min Smallest value before wrapping around to the largest value
254 * @param max Largest value before wrapping around to the smallest value
255 * @return Whether or not the actual value is within the allowed tolerance
256 */
257template <typename T>
258 requires std::is_arithmetic_v<T> || units::traits::is_unit_t_v<T>
259constexpr bool IsNear(T expected, T actual, T tolerance, T min, T max) {
260 T errorBound = (max - min) / 2.0;
261 T error = frc::InputModulus<T>(expected - actual, -errorBound, errorBound);
262
263 if constexpr (std::is_arithmetic_v<T>) {
264 return std::abs(error) < tolerance;
265 } else {
266 return units::math::abs(error) < tolerance;
267 }
268}
269
270/**
271 * Wraps an angle to the range -π to π radians (-180 to 180 degrees).
272 *
273 * @param angle Angle to wrap.
274 */
276constexpr units::radian_t AngleModulus(units::radian_t angle) {
278 units::radian_t{-std::numbers::pi},
279 units::radian_t{std::numbers::pi});
280}
281
282// floorDiv and floorMod algorithms taken from Java
283
284/**
285 * Returns the largest (closest to positive infinity)
286 * {@code int} value that is less than or equal to the algebraic quotient.
287 *
288 * @param x the dividend
289 * @param y the divisor
290 * @return the largest (closest to positive infinity)
291 * {@code int} value that is less than or equal to the algebraic quotient.
292 */
293constexpr std::signed_integral auto FloorDiv(std::signed_integral auto x,
294 std::signed_integral auto y) {
295 auto quot = x / y;
296 auto rem = x % y;
297 // if the signs are different and modulo not zero, round down
298 if ((x < 0) != (y < 0) && rem != 0) {
299 --quot;
300 }
301 return quot;
302}
303
304/**
305 * Returns the floor modulus of the {@code int} arguments.
306 * <p>
307 * The floor modulus is {@code r = x - (floorDiv(x, y) * y)},
308 * has the same sign as the divisor {@code y} or is zero, and
309 * is in the range of {@code -std::abs(y) < r < +std::abs(y)}.
310 *
311 * @param x the dividend
312 * @param y the divisor
313 * @return the floor modulus {@code x - (floorDiv(x, y) * y)}
314 */
315constexpr std::signed_integral auto FloorMod(std::signed_integral auto x,
316 std::signed_integral auto y) {
317 return x - FloorDiv(x, y) * y;
318}
319
320/**
321 * Limits translation velocity.
322 *
323 * @param current Translation at current timestep.
324 * @param next Translation at next timestep.
325 * @param dt Timestep duration.
326 * @param maxVelocity Maximum translation velocity.
327 * @return Returns the next Translation2d limited to maxVelocity
328 */
330 const Translation2d& next,
331 units::second_t dt,
332 units::meters_per_second_t maxVelocity) {
333 if (maxVelocity < 0_mps) {
335 "maxVelocity must be a non-negative number, got {}!", maxVelocity);
336 return next;
337 }
338 Translation2d diff = next - current;
339 units::meter_t dist = diff.Norm();
340 if (dist < 1e-9_m) {
341 return next;
342 }
343 if (dist > maxVelocity * dt) {
344 // Move maximum allowed amount in direction of the difference
345 // NOLINTNEXTLINE(bugprone-integer-division)
346 return current + diff * (maxVelocity * dt / dist);
347 }
348 return next;
349}
350
351/**
352 * Limits translation velocity.
353 *
354 * @param current Translation at current timestep.
355 * @param next Translation at next timestep.
356 * @param dt Timestep duration.
357 * @param maxVelocity Maximum translation velocity.
358 * @return Returns the next Translation3d limited to maxVelocity
359 */
361 const Translation3d& next,
362 units::second_t dt,
363 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 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 frc
#define WPILIB_DLLEXPORT
Definition SymbolExports.h:36
Represents a translation in 2D space.
Definition Translation2d.h:30
constexpr units::meter_t Norm() const
Returns the norm, or distance from the origin to the translation.
Definition Translation2d.h:124
Represents a translation in 3D space.
Definition Translation3d.h:31
constexpr units::meter_t Norm() const
Returns the norm, or distance from the origin to the translation.
Definition Translation3d.h:153
static void ReportError(const S &format, Args &&... args)
Definition MathShared.h:48
Definition SystemServer.h:9
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.h:38
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.h:315
WPILIB_DLLEXPORT constexpr units::radian_t AngleModulus(units::radian_t angle)
Wraps an angle to the range -π to π radians (-180 to 180 degrees).
Definition MathUtil.h:276
constexpr bool IsNear(T expected, T actual, T tolerance)
Checks if the given value matches an expected value within a certain tolerance.
Definition MathUtil.h:230
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.h:293
constexpr Translation2d SlewRateLimit(const Translation2d &current, const Translation2d &next, units::second_t dt, units::meters_per_second_t maxVelocity)
Limits translation velocity.
Definition MathUtil.h:329
constexpr T InputModulus(T input, T minimumInput, T maximumInput)
Returns modulus of input.
Definition MathUtil.h:204
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.h:146
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