Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 1 | #ifndef MOTORS_MATH_H_ |
| 2 | #define MOTORS_MATH_H_ |
| 3 | |
| 4 | #include <limits.h> |
| 5 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 6 | #include <array> |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 7 | #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 Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame^] | 13 | namespace frc971::motors { |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 14 | |
| 15 | inline constexpr unsigned int Log2RoundUp(unsigned int x) { |
| 16 | return (x < 2) ? x : (1 + Log2RoundUp(x / 2)); |
| 17 | } |
| 18 | |
| 19 | template <typename T> |
| 20 | inline constexpr const T &ConstexprMax(const T &a, const T &b) { |
| 21 | return (a < b) ? b : a; |
| 22 | } |
| 23 | |
| 24 | namespace math_internal { |
| 25 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 26 | constexpr uint32_t SinCosFloatTableSize() { return 2048; } |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 27 | |
| 28 | constexpr float FloatMaxMagnitude() { return 1.0f; } |
| 29 | |
| 30 | constexpr bool IsPowerOf2(uint32_t value) { |
| 31 | return value == (1u << (Log2RoundUp(value) - 1)); |
| 32 | } |
| 33 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 34 | static_assert(IsPowerOf2(SinCosFloatTableSize()), |
| 35 | "Tables need to be a power of 2"); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 36 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 37 | extern float sin_float_table[SinCosFloatTableSize() + 1]; |
| 38 | extern float cos_float_table[SinCosFloatTableSize() + 1]; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 39 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 40 | template <class Rotation, int kTableSize> |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 41 | float 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 Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 47 | ConstexprMax<uint32_t>(Rotation::den, kTableSize) * Rotation::num < |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 48 | UINT32_MAX, |
| 49 | "Numerator and denominator are too big"); |
| 50 | |
| 51 | // Rounding/truncating here isn't supported. |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 52 | static_assert(Rotation::den <= kTableSize, "Tables need to be bigger"); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 53 | |
| 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 Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 58 | constexpr uint32_t kDenominatorRatio = kTableSize / Rotation::den; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 59 | |
| 60 | // These should always be true given the other constraints. |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 61 | static_assert(kDenominatorRatio * Rotation::den == kTableSize, |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 62 | "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 Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 67 | kTableSize]; |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | inline float FastTableLookupFloat(float theta, const float *table) { |
Brian Silverman | a6e53b4 | 2018-01-03 20:33:15 -0800 | [diff] [blame] | 71 | static constexpr float kScalar = |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 72 | (SinCosFloatTableSize() / 2) / FloatMaxMagnitude(); |
Brian Silverman | a6e53b4 | 2018-01-03 20:33:15 -0800 | [diff] [blame] | 73 | const int index = |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 74 | (SinCosFloatTableSize() / 2) + static_cast<int32_t>(theta * kScalar); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 75 | return table[index]; |
| 76 | } |
| 77 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 78 | // 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. |
| 82 | class 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. |
| 99 | extern ::std::array<GenericInitializer *, 10> global_initializers; |
| 100 | |
| 101 | // Manages initializing and access to an integer table of a single size. |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 102 | template <int kTableSize> |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 103 | class 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 | |
| 150 | template <int kTableSize> |
| 151 | typename SinCosIntTable<kTableSize>::AddMyInitializer |
| 152 | SinCosIntTable<kTableSize>::add_my_initializer; |
| 153 | |
| 154 | template <int kTableSize> |
| 155 | float SinCosIntTable<kTableSize>::static_sin_int_table[kTableSize]; |
| 156 | template <int kTableSize> |
| 157 | float SinCosIntTable<kTableSize>::static_cos_int_table[kTableSize]; |
| 158 | |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 159 | } // namespace math_internal |
| 160 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 161 | // theta must be in [-0.2, 0.2]. |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 162 | inline float FastSinFloat(float theta) { |
| 163 | return math_internal::FastTableLookupFloat(theta, |
| 164 | math_internal::sin_float_table); |
| 165 | } |
| 166 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 167 | // theta must be in [-0.2, 0.2]. |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 168 | inline float FastCosFloat(float theta) { |
| 169 | return math_internal::FastTableLookupFloat(theta, |
| 170 | math_internal::cos_float_table); |
| 171 | } |
| 172 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 173 | // theta must be in [-0.2, 0.2]. |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 174 | inline ::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 Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 188 | // |
| 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 Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 193 | |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 194 | template <class Rotation, int kTableSize = Rotation::den> |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 195 | float FastSinInt(uint32_t theta) { |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 196 | return math_internal::FastTableLookupInt<Rotation, kTableSize>( |
| 197 | theta, math_internal::SinCosIntTable<kTableSize>::sin_int_table()); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 198 | } |
| 199 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 200 | template <class Rotation, int kTableSize = Rotation::den> |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 201 | float FastCosInt(uint32_t theta) { |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 202 | return math_internal::FastTableLookupInt<Rotation, kTableSize>( |
| 203 | theta, math_internal::SinCosIntTable<kTableSize>::cos_int_table()); |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 204 | } |
| 205 | |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 206 | template <class Rotation> |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 207 | ::std::complex<float> ImaginaryExpInt(uint32_t theta) { |
| 208 | return ::std::complex<float>(FastCosInt<Rotation>(theta), |
| 209 | FastSinInt<Rotation>(theta)); |
| 210 | } |
| 211 | |
Brian Silverman | 9ccec6e | 2018-10-21 22:06:35 -0700 | [diff] [blame] | 212 | // This must be called before any of the other functions. |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 213 | void MathInit(); |
| 214 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame^] | 215 | } // namespace frc971::motors |
Brian Silverman | 8d3816a | 2017-07-03 18:52:15 -0700 | [diff] [blame] | 216 | |
| 217 | #endif // MOTORS_MATH_H_ |