11#include < gtest/gtest.h>
2+ #include < eigen3/Eigen/Dense>
3+ #include < eigen3/Eigen/Geometry>
24#include " vortex/utils/math.hpp"
35
46namespace vortex ::utils::math {
@@ -30,7 +32,7 @@ TEST(get_skew_symmetric_matrix, test_skew_symmetric) {
3032 expected << 0 , -3 , 2 , 3 , 0 , -1 , -2 , 1 , 0 ;
3133
3234 Eigen::Matrix3d result = get_skew_symmetric_matrix (vector);
33- EXPECT_EQ ( expected, result );
35+ EXPECT_TRUE (result. isApprox ( expected, 1e-12 ) );
3436}
3537
3638// Test that rotation matrix is correctly constructed
@@ -42,14 +44,14 @@ TEST(get_rotation_matrix, test_rotation_matrix) {
4244 expected << 0.41198225 , -0.83373765 , -0.36763046 , -0.05872664 , -0.42691762 ,
4345 0.90238159 , -0.90929743 , -0.35017549 , -0.2248451 ;
4446 Eigen::Matrix3d result = get_rotation_matrix (roll, pitch, yaw);
45- EXPECT_NEAR ( 0.0 , matrix_norm_diff (expected, result), 0.01 );
47+ EXPECT_TRUE (result. isApprox (expected, 1e-8 ) );
4648}
4749
4850TEST (get_transformation_matrix_attitude, test_transformation_matrix_zeros) {
4951 Eigen::Matrix3d transformation_matrix{
5052 get_transformation_matrix_attitude (0.0 , 0.0 )};
5153 Eigen::Matrix3d expected{Eigen::Matrix3d::Identity ()};
52- EXPECT_NEAR ( 0.0 , matrix_norm_diff (expected, transformation_matrix), 0.01 );
54+ EXPECT_TRUE (transformation_matrix. isApprox (expected, 1e-12 ) );
5355}
5456
5557TEST (get_transformation_matrix_attitude_quat,
@@ -59,19 +61,15 @@ TEST(get_transformation_matrix_attitude_quat,
5961 get_transformation_matrix_attitude_quat (quat)};
6062 Eigen::Matrix<double , 4 , 3 > expected = Eigen::Matrix<double , 4 , 3 >::Zero ();
6163 expected.bottomRightCorner <3 , 3 >() = 0.5 * Eigen::Matrix3d::Identity ();
62- EXPECT_NEAR ( 0.0 , matrix_norm_diff (expected, transformation_matrix), 0.01 );
64+ EXPECT_TRUE (transformation_matrix. isApprox (expected, 1e-12 ) );
6365}
6466
6567TEST (eigen_vector3d_to_quaternion, zero_vector_returns_identity) {
6668 const Eigen::Vector3d v = Eigen::Vector3d::Zero ();
6769
6870 const Eigen::Quaterniond q = eigen_vector3d_to_quaternion (v);
6971
70- EXPECT_NEAR (q.w (), 1.0 , 1e-15 );
71- EXPECT_NEAR (q.x (), 0.0 , 1e-15 );
72- EXPECT_NEAR (q.y (), 0.0 , 1e-15 );
73- EXPECT_NEAR (q.z (), 0.0 , 1e-15 );
74- EXPECT_NEAR (q.norm (), 1.0 , 1e-15 );
72+ EXPECT_TRUE (q.isApprox (Eigen::Quaterniond::Identity (), 1e-12 ));
7573}
7674
7775TEST (eigen_vector3d_to_quaternion, general_vector_matches_axis_angle_formula) {
@@ -87,51 +85,39 @@ TEST(eigen_vector3d_to_quaternion, general_vector_matches_axis_angle_formula) {
8785
8886 const Eigen::Quaterniond q = eigen_vector3d_to_quaternion (v);
8987
90- EXPECT_NEAR (q.w (), expected.w (), 1e-12 );
91- EXPECT_NEAR (q.x (), expected.x (), 1e-12 );
92- EXPECT_NEAR (q.y (), expected.y (), 1e-12 );
93- EXPECT_NEAR (q.z (), expected.z (), 1e-12 );
94- EXPECT_NEAR (q.norm (), 1.0 , 1e-12 );
88+ EXPECT_TRUE (q.isApprox (expected, 1e-12 ));
9589}
9690
9791// Test that the identity quaternion correctly maps to 0, 0, 0
9892TEST (quat_to_euler, test_quat_to_euler_1) {
9993 Eigen::Quaterniond q (1.0 , 0.0 , 0.0 , 0.0 );
10094 Eigen::Vector3d expected (0.0 , 0.0 , 0.0 );
10195 Eigen::Vector3d result = quat_to_euler (q);
102- for (int i = 0 ; i < 3 ; ++i) {
103- EXPECT_NEAR (expected[i], result[i], 0.01 );
104- }
96+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
10597}
10698
10799// Test that only changing w and x in the quat only affects roll
108100TEST (quat_to_euler, test_quat_to_euler_2) {
109101 Eigen::Quaterniond q2 (0.707 , 0.707 , 0.0 , 0.0 );
110102 Eigen::Vector3d expected2 (1.57 , 0.0 , 0.0 );
111103 Eigen::Vector3d result2 = quat_to_euler (q2);
112- for (int i = 0 ; i < 3 ; ++i) {
113- EXPECT_NEAR (expected2[i], result2[i], 0.01 );
114- }
104+ EXPECT_TRUE (result2.isApprox (expected2, 1e-3 ));
115105}
116106
117107// Test that only changing w and z in the quat only affects yaw
118108TEST (quat_to_euler, test_quat_to_euler_3) {
119- Eigen::Quaterniond q4 (0.707 , 0.0 , 0.0 , 0.707 );
120- Eigen::Vector3d expected4 (0.0 , 0.0 , 1.57 );
121- Eigen::Vector3d result4 = quat_to_euler (q4);
122- for (int i = 0 ; i < 3 ; ++i) {
123- EXPECT_NEAR (expected4[i], result4[i], 0.01 );
124- }
109+ Eigen::Quaterniond q3 (0.707 , 0.0 , 0.0 , 0.707 );
110+ Eigen::Vector3d expected3 (0.0 , 0.0 , 1.57 );
111+ Eigen::Vector3d result3 = quat_to_euler (q3);
112+ EXPECT_TRUE (result3.isApprox (expected3, 1e-3 ));
125113}
126114
127115// Test that a quaternion is correctly converted to euler angles
128116TEST (quat_to_euler, test_quat_to_euler_4) {
129- Eigen::Quaterniond q5 (0.770 , 0.4207 , -0.4207 , -0.229 );
130- Eigen::Vector3d expected5 (1.237 , -0.4729 , -0.9179 );
131- Eigen::Vector3d result5 = quat_to_euler (q5);
132- for (int i = 0 ; i < 3 ; ++i) {
133- EXPECT_NEAR (expected5[i], result5[i], 0.01 );
134- }
117+ Eigen::Quaterniond q4 (0.770 , 0.4207 , -0.4207 , -0.229 );
118+ Eigen::Vector3d expected4 (1.237 , -0.4729 , -0.9179 );
119+ Eigen::Vector3d result4 = quat_to_euler (q4);
120+ EXPECT_TRUE (result4.isApprox (expected4, 1e-3 ));
135121}
136122
137123// Test that a quaternion with flipped signs is correctly converted to euler
@@ -140,9 +126,8 @@ TEST(quat_to_euler, test_quat_to_euler_5) {
140126 Eigen::Quaterniond q5 (0.770 , 0.4207 , 0.4207 , 0.229 );
141127 Eigen::Vector3d expected5 (1.237 , 0.4729 , 0.9179 );
142128 Eigen::Vector3d result5 = quat_to_euler (q5);
143- for (int i = 0 ; i < 3 ; ++i) {
144- EXPECT_NEAR (expected5[i], result5[i], 0.01 );
145- }
129+
130+ EXPECT_TRUE (result5.isApprox (expected5, 1e-3 ));
146131}
147132
148133// Test that zero euler angles construct the correct quaternion
@@ -153,9 +138,8 @@ TEST(euler_to_quat, test_euler_to_quat_1) {
153138 Eigen::Quaterniond q{euler_to_quat (roll, pitch, yaw)};
154139 Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
155140 Eigen::Vector4d expected{0.0 , 0.0 , 0.0 , 1.0 };
156- for (int i = 0 ; i < 4 ; ++i) {
157- EXPECT_NEAR (expected[i], result[i], 0.01 );
158- }
141+
142+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
159143}
160144
161145// Test that non-zero roll constructs the correct quaternion
@@ -166,9 +150,8 @@ TEST(euler_to_quat, test_euler_to_quat_2) {
166150 Eigen::Quaterniond q{euler_to_quat (roll, pitch, yaw)};
167151 Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
168152 Eigen::Vector4d expected{0.479 , 0.0 , 0.0 , 0.877 };
169- for (int i = 0 ; i < 4 ; ++i) {
170- EXPECT_NEAR (expected[i], result[i], 0.01 );
171- }
153+
154+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
172155}
173156
174157// Test that non-zero pitch constructs the correct quaternion
@@ -179,9 +162,8 @@ TEST(euler_to_quat, test_euler_to_quat_3) {
179162 Eigen::Quaterniond q{euler_to_quat (roll, pitch, yaw)};
180163 Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
181164 Eigen::Vector4d expected{0.0 , 0.479 , 0.0 , 0.877 };
182- for (int i = 0 ; i < 4 ; ++i) {
183- EXPECT_NEAR (expected[i], result[i], 0.01 );
184- }
165+
166+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
185167}
186168
187169// Test that non-zero yaw constructs the correct quaternion
@@ -192,9 +174,8 @@ TEST(euler_to_quat, test_euler_to_quat_4) {
192174 Eigen::Quaterniond q{euler_to_quat (roll, pitch, yaw)};
193175 Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
194176 Eigen::Vector4d expected{0.0 , 0.0 , 0.479 , 0.877 };
195- for (int i = 0 ; i < 4 ; ++i) {
196- EXPECT_NEAR (expected[i], result[i], 0.01 );
197- }
177+
178+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
198179}
199180
200181// Test that non-zero euler angles constructs the correct quaternion
@@ -205,9 +186,8 @@ TEST(euler_to_quat, test_euler_to_quat_5) {
205186 Eigen::Quaterniond q{euler_to_quat (roll, pitch, yaw)};
206187 Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
207188 Eigen::Vector4d expected{0.1675 , 0.5709 , 0.1675 , 0.786 };
208- for (int i = 0 ; i < 4 ; ++i) {
209- EXPECT_NEAR (expected[i], result[i], 0.01 );
210- }
189+
190+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
211191}
212192
213193// Test that zero euler angles construct the correct quaternion
@@ -218,9 +198,7 @@ TEST(euler_to_quat_vec3, zero_angles_identity_quaternion) {
218198 const Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
219199 const Eigen::Vector4d expected (0.0 , 0.0 , 0.0 , 1.0 );
220200
221- for (int i = 0 ; i < 4 ; ++i) {
222- EXPECT_NEAR (expected[i], result[i], 0.01 );
223- }
201+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
224202}
225203
226204// Test that non-zero roll constructs the correct quaternion
@@ -231,9 +209,7 @@ TEST(euler_to_quat_vec3, roll_only) {
231209 const Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
232210 const Eigen::Vector4d expected (0.479 , 0.0 , 0.0 , 0.877 );
233211
234- for (int i = 0 ; i < 4 ; ++i) {
235- EXPECT_NEAR (expected[i], result[i], 0.01 );
236- }
212+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
237213}
238214
239215// Test that non-zero pitch constructs the correct quaternion
@@ -244,9 +220,7 @@ TEST(euler_to_quat_vec3, pitch_only) {
244220 const Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
245221 const Eigen::Vector4d expected (0.0 , 0.479 , 0.0 , 0.877 );
246222
247- for (int i = 0 ; i < 4 ; ++i) {
248- EXPECT_NEAR (expected[i], result[i], 0.01 );
249- }
223+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
250224}
251225
252226// Test that non-zero yaw constructs the correct quaternion
@@ -257,9 +231,7 @@ TEST(euler_to_quat_vec3, yaw_only) {
257231 const Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
258232 const Eigen::Vector4d expected (0.0 , 0.0 , 0.479 , 0.877 );
259233
260- for (int i = 0 ; i < 4 ; ++i) {
261- EXPECT_NEAR (expected[i], result[i], 0.01 );
262- }
234+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
263235}
264236
265237// Test that non-zero euler angles construct the correct quaternion
@@ -270,9 +242,63 @@ TEST(euler_to_quat_vec3, roll_pitch_yaw_all_nonzero) {
270242 const Eigen::Vector4d result{q.x (), q.y (), q.z (), q.w ()};
271243 const Eigen::Vector4d expected (0.1675 , 0.5709 , 0.1675 , 0.786 );
272244
273- for (int i = 0 ; i < 4 ; ++i) {
274- EXPECT_NEAR (expected[i], result[i], 0.01 );
275- }
245+ EXPECT_TRUE (result.isApprox (expected, 1e-3 ));
246+ }
247+
248+ // Test pseudo inverse, results from
249+ // https://www.emathhelp.net/calculators/linear-algebra/pseudoinverse-calculator
250+ TEST (pseudo_inverse, pseudo_inverse_of_square_matrix_same_as_inverse) {
251+ Eigen::MatrixXd matrix (2 , 2 );
252+ matrix << 4 , 7 , 2 , 6 ;
253+ Eigen::MatrixXd expected = matrix.inverse ();
254+ Eigen::MatrixXd result = pseudo_inverse (matrix);
255+ EXPECT_TRUE (expected.isApprox (result, 1e-10 ));
256+ }
257+
258+ TEST (pseudo_inverse, pseudo_inverse_of_2_by_3_matrix) {
259+ Eigen::MatrixXd matrix (2 , 3 );
260+ matrix << 1 , 2 , 3 , 4 , 5 , 6 ;
261+ Eigen::MatrixXd expected (3 , 2 );
262+ expected << -17.0 / 18.0 , 4.0 / 9.0 , -1.0 / 9.0 , 1.0 / 9.0 , 13.0 / 18.0 ,
263+ -2.0 / 9.0 ;
264+ Eigen::MatrixXd result = pseudo_inverse (matrix);
265+ EXPECT_TRUE (expected.isApprox (result, 1e-10 ));
266+ }
267+
268+ TEST (pseudo_inverse, pseudo_inverse_of_3_by_2_matrix) {
269+ Eigen::MatrixXd matrix (3 , 2 );
270+ matrix << 1 , 2 , 3 , 4 , 5 , 6 ;
271+ Eigen::MatrixXd expected (2 , 3 );
272+ expected << -4.0 / 3.0 , -1.0 / 3.0 , 2.0 / 3.0 , 13.0 / 12.0 , 1.0 / 3.0 ,
273+ -5.0 / 12.0 ;
274+ Eigen::MatrixXd result = pseudo_inverse (matrix);
275+ EXPECT_TRUE (expected.isApprox (result, 1e-10 ));
276+ }
277+
278+ TEST (clamp_values, test_clamp_values) {
279+ Eigen::Vector<double , 5 > values;
280+ values << -10.0 , -1.0 , 0.0 , 1.0 , 10.0 ;
281+ double min_val = -5.0 ;
282+ double max_val = 5.0 ;
283+ Eigen::Vector<double , 5 > expected;
284+ expected << -5.0 , -1.0 , 0.0 , 1.0 , 5.0 ;
285+ Eigen::Vector<double , 5 > result = clamp_values (values, min_val, max_val);
286+ EXPECT_TRUE (expected.isApprox (result, 1e-10 ));
287+ }
288+
289+ TEST (anti_windup, test_anti_windup) {
290+ double dt = 0.1 ;
291+ Eigen::Vector<double , 3 > error;
292+ error << 1.0 , -2.0 , 3.0 ;
293+ Eigen::Vector<double , 3 > integral;
294+ integral << 0.5 , -0.5 , 0.5 ;
295+ double min_val = -0.5 ;
296+ double max_val = 0.7 ;
297+ Eigen::Vector<double , 3 > expected;
298+ expected << 0.6 , -0.5 , 0.7 ; // Last value clamped
299+ Eigen::Vector<double , 3 > result =
300+ anti_windup (dt, error, integral, min_val, max_val);
301+ EXPECT_TRUE (expected.isApprox (result, 1e-10 ));
276302}
277303
278304} // namespace vortex::utils::math
0 commit comments