Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 1 | #ifndef AOS_CONTROLS_QUATERNION_UTILS_H_ |
| 2 | #define AOS_CONTROLS_QUATERNION_UTILS_H_ |
| 3 | |
| 4 | #include "Eigen/Dense" |
| 5 | #include "Eigen/Geometry" |
Austin Schuh | 99f7c6a | 2024-06-25 22:07:44 -0700 | [diff] [blame] | 6 | #include "absl/log/check.h" |
| 7 | #include "absl/log/log.h" |
Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 8 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 9 | namespace frc971::controls { |
Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 10 | |
Jim Ostrowski | 6d242d5 | 2024-04-03 20:39:03 -0700 | [diff] [blame^] | 11 | // 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 |
| 13 | inline Eigen::Matrix<double, 4, 1> ExtractQuaternionMean( |
| 14 | Eigen::Matrix<double, 4, 4> input) { |
Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 15 | // Algorithm to compute the average of a bunch of quaternions: |
Jim Ostrowski | 6d242d5 | 2024-04-03 20:39:03 -0700 | [diff] [blame^] | 16 | // 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 Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 19 | |
| 20 | Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>> solver; |
Jim Ostrowski | 6d242d5 | 2024-04-03 20:39:03 -0700 | [diff] [blame^] | 21 | solver.compute(input); |
Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 22 | |
| 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 Ostrowski | 6d242d5 | 2024-04-03 20:39:03 -0700 | [diff] [blame^] | 48 | // 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). |
| 51 | template <int N> |
| 52 | inline 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} |
| 66 | inline 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 Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 79 | // 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. |
| 82 | Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion( |
| 83 | const Eigen::Matrix<double, 4, 1> &X); |
| 84 | |
| 85 | inline 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. |
| 93 | Eigen::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-u | e53bf55 | 2021-12-11 14:42:00 -0800 | [diff] [blame] | 97 | // 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 |
| 103 | inline 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 Ostrowski | 6d242d5 | 2024-04-03 20:39:03 -0700 | [diff] [blame^] | 106 | // another resource on quaternion integration and derivatives. |
milind-u | e53bf55 | 2021-12-11 14:42:00 -0800 | [diff] [blame] | 107 | 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 |
| 118 | Eigen::Matrix<double, 4, 3> QuaternionDerivativeDerivitive( |
| 119 | const Eigen::Vector4d &q_matrix); |
| 120 | inline Eigen::Matrix<double, 4, 3> QuaternionDerivativeDerivitive( |
| 121 | const Eigen::Quaternion<double> &q) { |
| 122 | return QuaternionDerivativeDerivitive(q.coeffs()); |
| 123 | } |
| 124 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 125 | } // namespace frc971::controls |
Brian Silverman | dac0a4b | 2020-06-23 17:03:09 -0700 | [diff] [blame] | 126 | |
| 127 | #endif // AOS_CONTROLS_QUATERNION_UTILS_H_ |