blob: b9aba4ae8600b0d65561f6a19502763d1b55eaa6 [file] [log] [blame]
Brian Silverman8d3816a2017-07-03 18:52:15 -07001#ifndef MOTORS_MATH_H_
2#define MOTORS_MATH_H_
3
4#include <limits.h>
5
Brian Silverman9ccec6e2018-10-21 22:06:35 -07006#include <array>
Brian Silverman8d3816a2017-07-03 18:52:15 -07007#include <complex>
8#include <ratio>
9
10// This file has some specialized math functions useful for implementing our
11// controls in a minimal number of cycles.
12
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080013namespace frc971::motors {
Brian Silverman8d3816a2017-07-03 18:52:15 -070014
15inline constexpr unsigned int Log2RoundUp(unsigned int x) {
16 return (x < 2) ? x : (1 + Log2RoundUp(x / 2));
17}
18
19template <typename T>
20inline constexpr const T &ConstexprMax(const T &a, const T &b) {
21 return (a < b) ? b : a;
22}
23
24namespace math_internal {
25
Brian Silverman9ccec6e2018-10-21 22:06:35 -070026constexpr uint32_t SinCosFloatTableSize() { return 2048; }
Brian Silverman8d3816a2017-07-03 18:52:15 -070027
28constexpr float FloatMaxMagnitude() { return 1.0f; }
29
30constexpr bool IsPowerOf2(uint32_t value) {
31 return value == (1u << (Log2RoundUp(value) - 1));
32}
33
Brian Silverman9ccec6e2018-10-21 22:06:35 -070034static_assert(IsPowerOf2(SinCosFloatTableSize()),
35 "Tables need to be a power of 2");
Brian Silverman8d3816a2017-07-03 18:52:15 -070036
Brian Silverman9ccec6e2018-10-21 22:06:35 -070037extern float sin_float_table[SinCosFloatTableSize() + 1];
38extern float cos_float_table[SinCosFloatTableSize() + 1];
Brian Silverman8d3816a2017-07-03 18:52:15 -070039
Brian Silverman9ccec6e2018-10-21 22:06:35 -070040template <class Rotation, int kTableSize>
Brian Silverman8d3816a2017-07-03 18:52:15 -070041float FastTableLookupInt(uint32_t theta, const float *table) {
42 static_assert(IsPowerOf2(Rotation::den),
43 "Denominator needs to be a power of 2");
44
45 // Don't need to worry about the sizes of intermediates given this constraint.
46 static_assert(
Brian Silverman9ccec6e2018-10-21 22:06:35 -070047 ConstexprMax<uint32_t>(Rotation::den, kTableSize) * Rotation::num <
Brian Silverman8d3816a2017-07-03 18:52:15 -070048 UINT32_MAX,
49 "Numerator and denominator are too big");
50
51 // Rounding/truncating here isn't supported.
Brian Silverman9ccec6e2018-10-21 22:06:35 -070052 static_assert(Rotation::den <= kTableSize, "Tables need to be bigger");
Brian Silverman8d3816a2017-07-03 18:52:15 -070053
54 // Don't feel like thinking through the consequences of this not being true.
55 static_assert(Rotation::num > 0 && Rotation::den > 0,
56 "Need a positive ratio");
57
Brian Silverman9ccec6e2018-10-21 22:06:35 -070058 constexpr uint32_t kDenominatorRatio = kTableSize / Rotation::den;
Brian Silverman8d3816a2017-07-03 18:52:15 -070059
60 // These should always be true given the other constraints.
Brian Silverman9ccec6e2018-10-21 22:06:35 -070061 static_assert(kDenominatorRatio * Rotation::den == kTableSize,
Brian Silverman8d3816a2017-07-03 18:52:15 -070062 "Math is broken");
63 static_assert(IsPowerOf2(kDenominatorRatio), "Math is broken");
64
65 return table[(theta * kDenominatorRatio * Rotation::num +
66 kDenominatorRatio * Rotation::num / 2) %
Brian Silverman9ccec6e2018-10-21 22:06:35 -070067 kTableSize];
Brian Silverman8d3816a2017-07-03 18:52:15 -070068}
69
70inline float FastTableLookupFloat(float theta, const float *table) {
Brian Silvermana6e53b42018-01-03 20:33:15 -080071 static constexpr float kScalar =
Brian Silverman9ccec6e2018-10-21 22:06:35 -070072 (SinCosFloatTableSize() / 2) / FloatMaxMagnitude();
Brian Silvermana6e53b42018-01-03 20:33:15 -080073 const int index =
Brian Silverman9ccec6e2018-10-21 22:06:35 -070074 (SinCosFloatTableSize() / 2) + static_cast<int32_t>(theta * kScalar);
Brian Silverman8d3816a2017-07-03 18:52:15 -070075 return table[index];
76}
77
Brian Silverman9ccec6e2018-10-21 22:06:35 -070078// A simple parent class for arranging to initialize all the templates.
79//
80// This will be lower overhead than ::std::function and also adds less
81// complicated stuff around static initialization time.
82class GenericInitializer {
83 public:
84 virtual void Initialize() = 0;
85
86 protected:
87 // Don't destroy instances via pointers to this type. Do it through subclass
88 // pointers instead.
89 ~GenericInitializer() = default;
90};
91
92// We build up this list at static construction time so we can call all the
93// contained objects in MathInit(). The contained pointers are not owned by this
94// list.
95//
96// This has to be big enough to hold all the different sizes of integer tables
97// somebody might use in one program. If it overflows, there will be a
98// __builtin_abort during static initialization.
99extern ::std::array<GenericInitializer *, 10> global_initializers;
100
101// Manages initializing and access to an integer table of a single size.
Philipp Schrader790cb542023-07-05 21:06:52 -0700102template <int kTableSize>
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700103class SinCosIntTable {
104 public:
105 static const float *sin_int_table() {
106 // Empirically, this is enough to get GCC to actually instantiate and link
107 // in the variable, without any runtime overhead.
108 (void)add_my_initializer;
109 return &static_sin_int_table[0];
110 }
111 static const float *cos_int_table() {
112 (void)add_my_initializer;
113 return &static_cos_int_table[0];
114 }
115
116 private:
117 // An object which adds a MyInitializer to global_initializers at static
118 // construction time.
119 class AddMyInitializer {
120 public:
121 AddMyInitializer() {
122 static MyInitializer initializer;
123 for (size_t i = 0; i < global_initializers.size(); ++i) {
124 if (global_initializers[i] == nullptr) {
125 global_initializers[i] = &initializer;
126 return;
127 }
128 }
129 __builtin_trap();
130 }
131 };
132
133 class MyInitializer : public GenericInitializer {
134 void Initialize() override {
135 for (uint32_t i = 0; i < kTableSize; ++i) {
136 const double int_theta =
137 ((static_cast<double>(i) + 0.5) / kTableSize) * 2.0 * M_PI;
138 static_sin_int_table[i] = sin(int_theta);
139 static_cos_int_table[i] = cos(int_theta);
140 }
141 }
142 };
143
144 static AddMyInitializer add_my_initializer;
145
146 static float static_sin_int_table[kTableSize];
147 static float static_cos_int_table[kTableSize];
148};
149
150template <int kTableSize>
151typename SinCosIntTable<kTableSize>::AddMyInitializer
152 SinCosIntTable<kTableSize>::add_my_initializer;
153
154template <int kTableSize>
155float SinCosIntTable<kTableSize>::static_sin_int_table[kTableSize];
156template <int kTableSize>
157float SinCosIntTable<kTableSize>::static_cos_int_table[kTableSize];
158
Brian Silverman8d3816a2017-07-03 18:52:15 -0700159} // namespace math_internal
160
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700161// theta must be in [-0.2, 0.2].
Brian Silverman8d3816a2017-07-03 18:52:15 -0700162inline float FastSinFloat(float theta) {
163 return math_internal::FastTableLookupFloat(theta,
164 math_internal::sin_float_table);
165}
166
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700167// theta must be in [-0.2, 0.2].
Brian Silverman8d3816a2017-07-03 18:52:15 -0700168inline float FastCosFloat(float theta) {
169 return math_internal::FastTableLookupFloat(theta,
170 math_internal::cos_float_table);
171}
172
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700173// theta must be in [-0.2, 0.2].
Brian Silverman8d3816a2017-07-03 18:52:15 -0700174inline ::std::complex<float> ImaginaryExpFloat(float theta) {
175 return ::std::complex<float>(FastCosFloat(theta), FastSinFloat(theta));
176}
177
178// The integer-based sin/cos functions all have a Rotation template argument,
179// which should be a ::std::ratio. The real argument to the trigonometric
180// function is this ratio multiplied by the theta argument.
181//
182// Specifically, they return the function evaluated at
183// (Rotation * (theta + 0.5)).
184//
185// All theta arguments must be in [0, Rotation::den).
186//
187// All denominators must be powers of 2.
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700188//
189// kTableSize is the size table it will actually use. This must be a power of 2
190// >= Rotation::den. The default of Rotation::den makes the most sense, unless
191// multiple denominators are needed in the same program, in which case using the
192// biggest denominator for all of them will use the least memory.
Brian Silverman8d3816a2017-07-03 18:52:15 -0700193
Philipp Schrader790cb542023-07-05 21:06:52 -0700194template <class Rotation, int kTableSize = Rotation::den>
Brian Silverman8d3816a2017-07-03 18:52:15 -0700195float FastSinInt(uint32_t theta) {
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700196 return math_internal::FastTableLookupInt<Rotation, kTableSize>(
197 theta, math_internal::SinCosIntTable<kTableSize>::sin_int_table());
Brian Silverman8d3816a2017-07-03 18:52:15 -0700198}
199
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700200template <class Rotation, int kTableSize = Rotation::den>
Brian Silverman8d3816a2017-07-03 18:52:15 -0700201float FastCosInt(uint32_t theta) {
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700202 return math_internal::FastTableLookupInt<Rotation, kTableSize>(
203 theta, math_internal::SinCosIntTable<kTableSize>::cos_int_table());
Brian Silverman8d3816a2017-07-03 18:52:15 -0700204}
205
Philipp Schrader790cb542023-07-05 21:06:52 -0700206template <class Rotation>
Brian Silverman8d3816a2017-07-03 18:52:15 -0700207::std::complex<float> ImaginaryExpInt(uint32_t theta) {
208 return ::std::complex<float>(FastCosInt<Rotation>(theta),
209 FastSinInt<Rotation>(theta));
210}
211
Brian Silverman9ccec6e2018-10-21 22:06:35 -0700212// This must be called before any of the other functions.
Brian Silverman8d3816a2017-07-03 18:52:15 -0700213void MathInit();
214
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800215} // namespace frc971::motors
Brian Silverman8d3816a2017-07-03 18:52:15 -0700216
217#endif // MOTORS_MATH_H_