blob: d7bbee43dc0ef088cc98a087ce2cd99977acbfbf [file] [log] [blame]
#ifndef MOTORS_MATH_H_
#define MOTORS_MATH_H_
#include <limits.h>
#include <array>
#include <complex>
#include <ratio>
// This file has some specialized math functions useful for implementing our
// controls in a minimal number of cycles.
namespace frc971 {
namespace motors {
inline constexpr unsigned int Log2RoundUp(unsigned int x) {
return (x < 2) ? x : (1 + Log2RoundUp(x / 2));
}
template <typename T>
inline constexpr const T &ConstexprMax(const T &a, const T &b) {
return (a < b) ? b : a;
}
namespace math_internal {
constexpr uint32_t SinCosFloatTableSize() { return 2048; }
constexpr float FloatMaxMagnitude() { return 1.0f; }
constexpr bool IsPowerOf2(uint32_t value) {
return value == (1u << (Log2RoundUp(value) - 1));
}
static_assert(IsPowerOf2(SinCosFloatTableSize()),
"Tables need to be a power of 2");
extern float sin_float_table[SinCosFloatTableSize() + 1];
extern float cos_float_table[SinCosFloatTableSize() + 1];
template <class Rotation, int kTableSize>
float FastTableLookupInt(uint32_t theta, const float *table) {
static_assert(IsPowerOf2(Rotation::den),
"Denominator needs to be a power of 2");
// Don't need to worry about the sizes of intermediates given this constraint.
static_assert(
ConstexprMax<uint32_t>(Rotation::den, kTableSize) * Rotation::num <
UINT32_MAX,
"Numerator and denominator are too big");
// Rounding/truncating here isn't supported.
static_assert(Rotation::den <= kTableSize, "Tables need to be bigger");
// Don't feel like thinking through the consequences of this not being true.
static_assert(Rotation::num > 0 && Rotation::den > 0,
"Need a positive ratio");
constexpr uint32_t kDenominatorRatio = kTableSize / Rotation::den;
// These should always be true given the other constraints.
static_assert(kDenominatorRatio * Rotation::den == kTableSize,
"Math is broken");
static_assert(IsPowerOf2(kDenominatorRatio), "Math is broken");
return table[(theta * kDenominatorRatio * Rotation::num +
kDenominatorRatio * Rotation::num / 2) %
kTableSize];
}
inline float FastTableLookupFloat(float theta, const float *table) {
static constexpr float kScalar =
(SinCosFloatTableSize() / 2) / FloatMaxMagnitude();
const int index =
(SinCosFloatTableSize() / 2) + static_cast<int32_t>(theta * kScalar);
return table[index];
}
// A simple parent class for arranging to initialize all the templates.
//
// This will be lower overhead than ::std::function and also adds less
// complicated stuff around static initialization time.
class GenericInitializer {
public:
virtual void Initialize() = 0;
protected:
// Don't destroy instances via pointers to this type. Do it through subclass
// pointers instead.
~GenericInitializer() = default;
};
// We build up this list at static construction time so we can call all the
// contained objects in MathInit(). The contained pointers are not owned by this
// list.
//
// This has to be big enough to hold all the different sizes of integer tables
// somebody might use in one program. If it overflows, there will be a
// __builtin_abort during static initialization.
extern ::std::array<GenericInitializer *, 10> global_initializers;
// Manages initializing and access to an integer table of a single size.
template<int kTableSize>
class SinCosIntTable {
public:
static const float *sin_int_table() {
// Empirically, this is enough to get GCC to actually instantiate and link
// in the variable, without any runtime overhead.
(void)add_my_initializer;
return &static_sin_int_table[0];
}
static const float *cos_int_table() {
(void)add_my_initializer;
return &static_cos_int_table[0];
}
private:
// An object which adds a MyInitializer to global_initializers at static
// construction time.
class AddMyInitializer {
public:
AddMyInitializer() {
static MyInitializer initializer;
for (size_t i = 0; i < global_initializers.size(); ++i) {
if (global_initializers[i] == nullptr) {
global_initializers[i] = &initializer;
return;
}
}
__builtin_trap();
}
};
class MyInitializer : public GenericInitializer {
void Initialize() override {
for (uint32_t i = 0; i < kTableSize; ++i) {
const double int_theta =
((static_cast<double>(i) + 0.5) / kTableSize) * 2.0 * M_PI;
static_sin_int_table[i] = sin(int_theta);
static_cos_int_table[i] = cos(int_theta);
}
}
};
static AddMyInitializer add_my_initializer;
static float static_sin_int_table[kTableSize];
static float static_cos_int_table[kTableSize];
};
template <int kTableSize>
typename SinCosIntTable<kTableSize>::AddMyInitializer
SinCosIntTable<kTableSize>::add_my_initializer;
template <int kTableSize>
float SinCosIntTable<kTableSize>::static_sin_int_table[kTableSize];
template <int kTableSize>
float SinCosIntTable<kTableSize>::static_cos_int_table[kTableSize];
} // namespace math_internal
// theta must be in [-0.2, 0.2].
inline float FastSinFloat(float theta) {
return math_internal::FastTableLookupFloat(theta,
math_internal::sin_float_table);
}
// theta must be in [-0.2, 0.2].
inline float FastCosFloat(float theta) {
return math_internal::FastTableLookupFloat(theta,
math_internal::cos_float_table);
}
// theta must be in [-0.2, 0.2].
inline ::std::complex<float> ImaginaryExpFloat(float theta) {
return ::std::complex<float>(FastCosFloat(theta), FastSinFloat(theta));
}
// The integer-based sin/cos functions all have a Rotation template argument,
// which should be a ::std::ratio. The real argument to the trigonometric
// function is this ratio multiplied by the theta argument.
//
// Specifically, they return the function evaluated at
// (Rotation * (theta + 0.5)).
//
// All theta arguments must be in [0, Rotation::den).
//
// All denominators must be powers of 2.
//
// kTableSize is the size table it will actually use. This must be a power of 2
// >= Rotation::den. The default of Rotation::den makes the most sense, unless
// multiple denominators are needed in the same program, in which case using the
// biggest denominator for all of them will use the least memory.
template<class Rotation, int kTableSize = Rotation::den>
float FastSinInt(uint32_t theta) {
return math_internal::FastTableLookupInt<Rotation, kTableSize>(
theta, math_internal::SinCosIntTable<kTableSize>::sin_int_table());
}
template <class Rotation, int kTableSize = Rotation::den>
float FastCosInt(uint32_t theta) {
return math_internal::FastTableLookupInt<Rotation, kTableSize>(
theta, math_internal::SinCosIntTable<kTableSize>::cos_int_table());
}
template<class Rotation>
::std::complex<float> ImaginaryExpInt(uint32_t theta) {
return ::std::complex<float>(FastCosInt<Rotation>(theta),
FastSinInt<Rotation>(theta));
}
// This must be called before any of the other functions.
void MathInit();
} // namespace motors
} // namespace frc971
#endif // MOTORS_MATH_H_