blob: c2c854cea35b5ec8c388d0058dde7f1b0f607d0c [file] [log] [blame]
Brian Silvermandac0a4b2020-06-23 17:03:09 -07001#ifndef AOS_CONTROLS_QUATERNION_UTILS_H_
2#define AOS_CONTROLS_QUATERNION_UTILS_H_
3
4#include "Eigen/Dense"
5#include "Eigen/Geometry"
Austin Schuh99f7c6a2024-06-25 22:07:44 -07006#include "absl/log/check.h"
7#include "absl/log/log.h"
Brian Silvermandac0a4b2020-06-23 17:03:09 -07008
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -08009namespace frc971::controls {
Brian Silvermandac0a4b2020-06-23 17:03:09 -070010
Jim Ostrowski6d242d52024-04-03 20:39:03 -070011// Helper function to extract mean quaternion from A*A^T of quaternion list
12// This allows us to support multiple formats of the input quaternion list
13inline Eigen::Matrix<double, 4, 1> ExtractQuaternionMean(
14 Eigen::Matrix<double, 4, 4> input) {
Brian Silvermandac0a4b2020-06-23 17:03:09 -070015 // Algorithm to compute the average of a bunch of quaternions:
Jim Ostrowski6d242d52024-04-03 20:39:03 -070016 // http://www.acsu.buffalo.edu/~johnc/ave_quat07.pdf
17 // See also:
18 // https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872.pdf
Brian Silvermandac0a4b2020-06-23 17:03:09 -070019
20 Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>> solver;
Jim Ostrowski6d242d52024-04-03 20:39:03 -070021 solver.compute(input);
Brian Silvermandac0a4b2020-06-23 17:03:09 -070022
23 Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>>::EigenvectorsType
24 eigenvectors = solver.eigenvectors();
25 Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>>::EigenvalueType eigenvalues =
26 solver.eigenvalues();
27
28 int max_index = 0;
29 double max_eigenvalue = 0.0;
30 for (int i = 0; i < 4; ++i) {
31 const double eigenvalue = std::abs(eigenvalues(i, 0));
32 if (eigenvalue > max_eigenvalue) {
33 max_eigenvalue = eigenvalue;
34 max_index = i;
35 }
36 }
37
38 // Assume that there shouldn't be any imaginary components to the eigenvector.
39 // I can't prove this is true, but everyone else seems to assume it...
40 // TODO(james): Handle this more rigorously.
41 for (int i = 0; i < 4; ++i) {
42 CHECK_LT(eigenvectors(i, max_index).imag(), 1e-4)
43 << eigenvectors(i, max_index);
44 }
45 return eigenvectors.col(max_index).real().normalized();
46}
47
Jim Ostrowski6d242d52024-04-03 20:39:03 -070048// Function to compute the quaternion average of a bunch of quaternions. Each
49// column in the 4xN input Matrix is a quaternion (optionally scaled by it's
50// weight).
51template <int N>
52inline Eigen::Matrix<double, 4, 1> QuaternionMean(
53 Eigen::Matrix<double, 4, N> quaternions) {
54 Eigen::Matrix<double, 4, 4> m = quaternions * quaternions.transpose();
55
56 return ExtractQuaternionMean(m);
57}
58
59// Function to compute the quaternion average of a bunch of quaternions.
60// This allows for passing in a variable size list (vector) of quaternions
61
62// For reference (since I've been bitten by it):
63// Eigen::Quaternion stores and prints coefficients as [x, y, z, w]
64// and initializes using a Vector4d in this order, BUT
65// initializes with scalars as Eigen::Quaternion{w, x, y, z}
66inline Eigen::Vector4d QuaternionMean(
67 std::vector<Eigen::Vector4d> quaternion_list) {
68 CHECK(quaternion_list.size() != 0)
69 << "Must have at least one quaternion to compute an average!";
70
71 Eigen::Matrix<double, 4, 4> m = Eigen::Matrix4d::Zero();
72 for (Eigen::Vector4d quaternion : quaternion_list) {
73 m += quaternion * quaternion.transpose();
74 }
75
76 return Eigen::Vector4d(ExtractQuaternionMean(m));
77}
78
Brian Silvermandac0a4b2020-06-23 17:03:09 -070079// Converts from a quaternion to a rotation vector, where the rotation vector's
80// direction represents the axis to rotate around and its magnitude represents
81// the number of radians to rotate.
82Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion(
83 const Eigen::Matrix<double, 4, 1> &X);
84
85inline Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion(
86 const Eigen::Quaternion<double> &X) {
87 return ToRotationVectorFromQuaternion(X.coeffs());
88}
89
90// Converts from a rotation vector to a quaternion. If you supply max_angle_cap,
91// then the rotation vector's magnitude will be clipped to be no more than
92// max_angle_cap before being converted to a quaternion.
93Eigen::Matrix<double, 4, 1> ToQuaternionFromRotationVector(
94 const Eigen::Matrix<double, 3, 1> &X,
95 const double max_angle_cap = std::numeric_limits<double>::infinity());
96
milind-ue53bf552021-12-11 14:42:00 -080097// Creates a rotational velocity vector to be integrated.
98//
99// omega is the rotational velocity vector in body coordinates.
100// q is a matrix with the compononents of the quaternion in it.
101//
102// Returns dq / dt
103inline Eigen::Vector4d QuaternionDerivative(Eigen::Vector3d omega,
104 const Eigen::Vector4d &q_matrix) {
105 // See https://www.ashwinnarayan.com/post/how-to-integrate-quaternions/ for
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700106 // another resource on quaternion integration and derivatives.
milind-ue53bf552021-12-11 14:42:00 -0800107 Eigen::Quaternion<double> q(q_matrix);
108
109 Eigen::Quaternion<double> omega_q;
110 omega_q.w() = 0.0;
111 omega_q.vec() = 0.5 * omega;
112
113 Eigen::Quaternion<double> deriv = q * omega_q;
114 return deriv.coeffs();
115}
116
117// d QuaternionDerivative / d omega
118Eigen::Matrix<double, 4, 3> QuaternionDerivativeDerivitive(
119 const Eigen::Vector4d &q_matrix);
120inline Eigen::Matrix<double, 4, 3> QuaternionDerivativeDerivitive(
121 const Eigen::Quaternion<double> &q) {
122 return QuaternionDerivativeDerivitive(q.coeffs());
123}
124
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800125} // namespace frc971::controls
Brian Silvermandac0a4b2020-06-23 17:03:09 -0700126
127#endif // AOS_CONTROLS_QUATERNION_UTILS_H_