EigenanalysisTest.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <AzCore/std/containers/array.h>
  10. #include <AzCore/std/utils.h>
  11. #include <NumericalMethods/Eigenanalysis.h>
  12. #include <Eigenanalysis/Solver3x3.h>
  13. #include <Eigenanalysis/Utilities.h>
  14. #include <Tests/Environment.h>
  15. #include <LinearAlgebra.h>
  16. namespace NumericalMethods::Eigenanalysis
  17. {
  18. // Structs for holding test cases. The allocator isn't ready when test cases are instantiated. The below structs do
  19. // not use any dynamic memory allocation.
  20. struct TestMatrix
  21. {
  22. const double m_00, m_01, m_02;
  23. const double m_11, m_12;
  24. const double m_22;
  25. };
  26. struct TestCase
  27. {
  28. TestMatrix m_matrix; // Test matrix.
  29. AZStd::array<Eigenpair<Real, 3>, 3> m_eigenpairs; // Expected eigenpairs.
  30. };
  31. // Small helper to allow creating an array without needing to specify its size.
  32. template <typename V, typename... T>
  33. constexpr auto CreateArrayOf(T&&... t) -> AZStd::array<V, sizeof...(T)>
  34. {
  35. return { { AZStd::forward<T>(t)... } };
  36. }
  37. // Test cases with unique eigenvalues. Eigenpairs must be sorted by eigenvalues in ascending order.
  38. auto testCasesUniqueEigenvalues = CreateArrayOf<TestCase>(
  39. TestCase{
  40. {
  41. -11, -3, 19,
  42. -15, -18,
  43. 18
  44. },
  45. {{
  46. { -25.8595477937, { -0.505754443439, 0.698786152511, 0.505875830615 } },
  47. { -15.8674241728, { -0.771059353507, -0.629147018687, 0.098191151571 } },
  48. { 33.7269719665, { 0.386884887674, -0.340399679696, 0.856999499272 } }
  49. }}
  50. },
  51. TestCase{
  52. {
  53. 1, 0, 1,
  54. 1, 0,
  55. 1
  56. },
  57. {{
  58. { 0.0, { -0.707106781187, 0.0, 0.707106781187 } },
  59. { 1.0, { 0.0, 1.0, 0.0 } },
  60. { 2.0, { 0.707106781187, 0.0, 0.707106781187 } }
  61. }}
  62. },
  63. TestCase{
  64. {
  65. 5, -2, -17,
  66. -4, -20,
  67. 15
  68. },
  69. {{
  70. { -21.6701752837, { 0.420221510597, 0.700470257632, 0.576849460608 } },
  71. { 3.4584421978, { 0.793094051356, -0.592408347576, 0.141612765759 } },
  72. { 34.2117330859, { -0.440925966274, -0.397987145389, 0.804481525189 } }
  73. }}
  74. },
  75. TestCase{
  76. {
  77. 19, -15, 6,
  78. -10, -6,
  79. -5
  80. },
  81. {{
  82. { -17.2954822996, { 0.326496938489, 0.902445817476, 0.281053901729 } },
  83. { -5.99734083922, { -0.326080759975, -0.171551806039, 0.92964580127 } },
  84. { 27.2928231388, { 0.887170269526, -0.395172777863, 0.238259078535 } }
  85. }}
  86. },
  87. TestCase{
  88. {
  89. -9, 4, -11,
  90. 2, 5,
  91. -17
  92. },
  93. {{
  94. { -26.1553450876, { 0.562432913498, -0.221379297992, 0.796655775247 } },
  95. { -1.32806716822, { -0.803682454314, 0.0800766638504, 0.589645860271 } },
  96. { 3.48341225584, { 0.19432892333, 0.971894507818, 0.132880906192 } }
  97. }}
  98. },
  99. TestCase{
  100. {
  101. 1, 10, 19,
  102. -16, -10,
  103. 18
  104. },
  105. {{
  106. { -27.7725713042, { -0.51831392659, 0.764992480984, 0.382278926363 } },
  107. { 0.250092094577, { -0.676678866396, -0.640202791396, 0.363656565542 } },
  108. { 30.5224792096, { 0.52293057405, -0.0701918081225, 0.849480267456 } }
  109. }}
  110. },
  111. TestCase{
  112. {
  113. 2, -20, -15,
  114. -18, 18,
  115. 15
  116. },
  117. {{
  118. { -31.8837451646, { -0.430872006429, -0.879972750147, 0.199993182569 } },
  119. { -6.43875739702, { 0.716635511483, -0.198972078689, 0.668463653151 } },
  120. { 37.3225025616, { -0.548436739978, 0.431344492142, 0.716351220661 } }
  121. }}
  122. },
  123. TestCase{
  124. {
  125. -9, 19, 15,
  126. 15, 16,
  127. 6
  128. },
  129. {{
  130. { -21.1504502799, { -0.893560928596, 0.339760284078, 0.293448149167 } },
  131. { -6.10937620187, { 0.0235549957329, -0.617262260016, 0.786404771435 } },
  132. { 39.2598264818, { 0.448323576295, 0.709612747718, 0.543558386205 } }
  133. }}
  134. },
  135. TestCase{
  136. {
  137. -7, -3, 16,
  138. 12, -9,
  139. 3
  140. },
  141. {{
  142. { -19.059434343, { -0.785269804824, 0.101151484629, 0.610835256668 } },
  143. { 4.45691526016, { 0.464300105322, 0.748872986542, 0.472879120099 } },
  144. { 22.6025190828, { 0.409605597898, -0.654948568351, 0.635031988947 } }
  145. }}
  146. },
  147. TestCase{
  148. {
  149. -9, -14, 15,
  150. 6, 4,
  151. -18
  152. },
  153. {{
  154. { -32.817456338, { -0.628492962187, -0.3005975342, 0.717382547121 } },
  155. { -3.26156142171, { 0.530405111203, 0.508970569411, 0.677952341601 } },
  156. { 15.0790177597, { 0.568917405684, -0.806591645076, 0.160445952281 } }
  157. }}
  158. }
  159. );
  160. // Test cases with unique eigenvalues. Eigenpairs must be sorted by eigenvalues in ascending order.
  161. auto testCasesRepeatedEigenvalues = CreateArrayOf<TestCase>(
  162. TestCase{
  163. {
  164. 1, 1, 1,
  165. 1, 1,
  166. 1
  167. },
  168. {{
  169. { 0.0, { -0.7071067811865475, 0.7071067811865475, 0.0 } },
  170. { 0.0, { 0.4082482904638630, 0.4082482904638630, -0.8164965809277260 } },
  171. { 3.0, { 0.5773502691896258, 0.5773502691896258, 0.5773502691896258 } }
  172. }}
  173. },
  174. TestCase{
  175. {
  176. 1.0, 0.0, 1.4142135623730950,
  177. 2.0, 0.0,
  178. 0.0
  179. },
  180. {{
  181. { -1.0, { -0.5773502691896257, 0.0, 0.8164965809277261 } },
  182. { 2.0, { 0.0, 1.0, 0.0 } },
  183. { 2.0, { 0.816496580927726, 0, 0.5773502691896258 } }
  184. }}
  185. }
  186. );
  187. // Array to vector conversion.
  188. AZStd::vector<double> ArrayToVector(AZStd::array<double, 3> data)
  189. {
  190. return AZStd::vector<double>(data.begin(), data.end());
  191. }
  192. // Helper to compare actual and expected eigenvectors. Both must be unit length but they may point in opposite
  193. // directions.
  194. void ExpectParallelUnitVector(
  195. const AZStd::vector<double>& actual, const AZStd::vector<double>& expected, const double tolerance
  196. )
  197. {
  198. const VectorVariable actualVector = VectorVariable::CreateFromVector(actual);
  199. const VectorVariable expectedVector = VectorVariable::CreateFromVector(expected);
  200. // Check length of vector.
  201. EXPECT_NEAR(actualVector.Norm(), 1.0, tolerance);
  202. // Check direction of vector.
  203. AZStd::vector<double> corrected(3, 0.0);
  204. if (actualVector.Dot(expectedVector) >= 0)
  205. {
  206. corrected = expected;
  207. }
  208. else
  209. {
  210. corrected = (expectedVector * (-1.0)).GetValues();
  211. }
  212. ExpectClose(actual, corrected, tolerance);
  213. }
  214. // Helper to test that a unit vector is a circular combination of two other unit vectors. All three unit vectors
  215. // must lie in the same plane.
  216. void ExpectLinearlyDependentUnitVector(
  217. const AZStd::vector<double>& actual,
  218. const AZStd::vector<double>& baseOne,
  219. const AZStd::vector<double>& baseTwo,
  220. const double tolerance
  221. )
  222. {
  223. const VectorVariable actualVector = VectorVariable::CreateFromVector(actual);
  224. const VectorVariable baseOneVector = VectorVariable::CreateFromVector(baseOne);
  225. const VectorVariable baseTwoVector = VectorVariable::CreateFromVector(baseTwo);
  226. // Check length of vector.
  227. EXPECT_NEAR(actualVector.Norm(), 1.0, 1e-6);
  228. // Check whether the actual vector is a circular combination of the two base vectors.
  229. double componentOne = baseOneVector.Dot(actualVector);
  230. double componentTwo = baseTwoVector.Dot(actualVector);
  231. EXPECT_NEAR(componentOne * componentOne + componentTwo * componentTwo, 1.0, tolerance);
  232. const VectorVariable composedVector = baseOneVector * componentOne + baseTwoVector * componentTwo;
  233. ExpectClose(actualVector, composedVector, tolerance);
  234. }
  235. // Helper to check that the returned eigenvectors form a right handed basis.
  236. void ExpectRightHandedOrthogonalBasis(
  237. const AZStd::array<double, 3>& x,
  238. const AZStd::array<double, 3>& y,
  239. const AZStd::array<double, 3>& z,
  240. const double tolerance
  241. )
  242. {
  243. const VectorVariable xVector = VectorVariable::CreateFromVector(ArrayToVector(x));
  244. const VectorVariable yVector = VectorVariable::CreateFromVector(ArrayToVector(y));
  245. const VectorVariable zVector = VectorVariable::CreateFromVector(ArrayToVector(z));
  246. ExpectClose(zVector, CrossProduct(xVector, yVector), tolerance);
  247. EXPECT_NEAR(xVector.Dot(yVector), 0.0, tolerance);
  248. EXPECT_NEAR(xVector.Dot(zVector), 0.0, tolerance);
  249. EXPECT_NEAR(yVector.Dot(zVector), 0.0, tolerance);
  250. }
  251. TEST(CrossProductTest, CrossProduct_CorrectResults)
  252. {
  253. VectorVariable v1 = VectorVariable::CreateFromVector({ 6.0, -5.0, 8.0 });
  254. VectorVariable v2 = VectorVariable::CreateFromVector({ -7.0, -4.0, 2.0 });
  255. VectorVariable v3 = VectorVariable::CreateFromVector({ 3.0, 7.0, -5.0 });
  256. ExpectClose(CrossProduct(v1, v2).GetValues(), { 22.0, -68.0, -59.0 }, 1e-3);
  257. ExpectClose(CrossProduct(v2, v1).GetValues(), { -22.0, 68.0, 59.0 }, 1e-3);
  258. ExpectClose(CrossProduct(v1, v3).GetValues(), { -31.0, 54.0, 57.0 }, 1e-3);
  259. ExpectClose(CrossProduct(v3, v1).GetValues(), { 31.0, -54.0, -57.0 }, 1e-3);
  260. ExpectClose(CrossProduct(v2, v3).GetValues(), { 6.0, -29.0, -37.0 }, 1e-3);
  261. ExpectClose(CrossProduct(v3, v2).GetValues(), { -6.0, 29.0, 37.0 }, 1e-3);
  262. ExpectClose(CrossProduct(v1, v1).GetValues(), { 0.0, 0.0, 0.0 }, 1e-3);
  263. ExpectClose(CrossProduct(v2, v2).GetValues(), { 0.0, 0.0, 0.0 }, 1e-3);
  264. ExpectClose(CrossProduct(v3, v3).GetValues(), { 0.0, 0.0, 0.0 }, 1e-3);
  265. }
  266. class OrthogonalComplementParams
  267. : public ::testing::TestWithParam<AZStd::array<double, 3>>
  268. {
  269. };
  270. TEST_P(OrthogonalComplementParams, OrthogonalComplement_CorrectResult)
  271. {
  272. VectorVariable v1 = VectorVariable::CreateFromVector(ArrayToVector(GetParam()));
  273. VectorVariable v2(3);
  274. VectorVariable v3(3);
  275. ComputeOrthogonalComplement(v1, v2, v3);
  276. ExpectClose(CrossProduct(v2, v3), v1, 1e-6);
  277. EXPECT_NEAR(v2.Norm(), 1.0, 1e-6);
  278. EXPECT_NEAR(v3.Norm(), 1.0, 1e-6);
  279. }
  280. INSTANTIATE_TEST_SUITE_P(
  281. All,
  282. OrthogonalComplementParams,
  283. ::testing::Values(
  284. // The allocator isn't ready when the code below is executed. As a workaround, use a fixed size arrays.
  285. AZStd::array<double, 3>({ 1.0, 0.0, 0.0 }),
  286. AZStd::array<double, 3>({ 0.0, 1.0, 0.0 }),
  287. AZStd::array<double, 3>({ 0.0, 0.0, 1.0 }),
  288. AZStd::array<double, 3>({ 0.801784, 0.534522, -0.267261 })
  289. )
  290. );
  291. class ComputeEigenvector0Params
  292. : public ::testing::TestWithParam<TestCase>
  293. {
  294. };
  295. TEST_P(ComputeEigenvector0Params, ComputeEigenvector0_CorrectResult)
  296. {
  297. const TestCase& testCase = GetParam();
  298. // The eigenvalues of the test matrices are spaced sufficiently far apart, so we can use this algorithm to
  299. // compute all eigenvectors.
  300. for (const auto& expected : testCase.m_eigenpairs)
  301. {
  302. const VectorVariable actual = ComputeEigenvector0(
  303. testCase.m_matrix.m_00,
  304. testCase.m_matrix.m_01,
  305. testCase.m_matrix.m_02,
  306. testCase.m_matrix.m_11,
  307. testCase.m_matrix.m_12,
  308. testCase.m_matrix.m_22,
  309. expected.m_value
  310. );
  311. // The computed eigenvector must either be parallel or anti-parallel to the expected eigenvector.
  312. ExpectParallelUnitVector(actual.GetValues(), ArrayToVector(expected.m_vector), 1e-6);
  313. }
  314. }
  315. INSTANTIATE_TEST_SUITE_P(All, ComputeEigenvector0Params, ::testing::ValuesIn(testCasesUniqueEigenvalues));
  316. class ComputeEigenvector1UniqueEigenvalueParams
  317. : public ::testing::TestWithParam<TestCase>
  318. {
  319. };
  320. TEST_P(ComputeEigenvector1UniqueEigenvalueParams, ComputeEigenvector1_CorrectResultForUniqueEigenvalue)
  321. {
  322. const TestCase& testCase = GetParam();
  323. // The algorithm to find the second eigenvector relies on one eigenvector being known already. To test this
  324. // function fully, each eigenvector plays the role of known and expected vector in turn until all combinations
  325. // are exhausted.
  326. for (auto i = testCase.m_eigenpairs.begin(); i != testCase.m_eigenpairs.end(); ++i)
  327. {
  328. for (auto j = testCase.m_eigenpairs.begin(); j != testCase.m_eigenpairs.end(); ++j)
  329. {
  330. if (i != j)
  331. {
  332. const VectorVariable knownVec = VectorVariable::CreateFromVector(ArrayToVector(i->m_vector));
  333. const double knownVal = j->m_value;
  334. const VectorVariable actual = ComputeEigenvector1(
  335. testCase.m_matrix.m_00,
  336. testCase.m_matrix.m_01,
  337. testCase.m_matrix.m_02,
  338. testCase.m_matrix.m_11,
  339. testCase.m_matrix.m_12,
  340. testCase.m_matrix.m_22,
  341. knownVal,
  342. knownVec
  343. );
  344. // The computed eigenvector must either be parallel or anti-parallel to the expected eigenvector.
  345. ExpectParallelUnitVector(actual.GetValues(), ArrayToVector(j->m_vector), 1e-6);
  346. }
  347. }
  348. }
  349. }
  350. INSTANTIATE_TEST_SUITE_P(
  351. All,
  352. ComputeEigenvector1UniqueEigenvalueParams,
  353. ::testing::ValuesIn(testCasesUniqueEigenvalues)
  354. );
  355. class ComputeEigenvector1RepeatedEigenvalueParams
  356. : public ::testing::TestWithParam<TestCase>
  357. {
  358. };
  359. TEST_P(ComputeEigenvector1RepeatedEigenvalueParams, ComputeEigenvector1_CorrectResultForRepeatedEigenvalue)
  360. {
  361. const TestCase& testCase = GetParam();
  362. // Get the index of the eigenpair with the unique eigenvalue.
  363. int uniqueIndex = testCase.m_eigenpairs[0].m_value == testCase.m_eigenpairs[1].m_value ? 2 : 0;
  364. const VectorVariable knownVec = VectorVariable::CreateFromVector(
  365. ArrayToVector(testCase.m_eigenpairs[uniqueIndex].m_vector)
  366. );
  367. const double knownVal = testCase.m_eigenpairs[(uniqueIndex + 1) % 3].m_value;
  368. const VectorVariable actual = ComputeEigenvector1(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, knownVal, knownVec);
  369. // The computed eigenvalue must be a circular combination of the two expected eigenvectors corresponding to the
  370. // repeated eigenvalue.
  371. ExpectLinearlyDependentUnitVector(
  372. actual.GetValues(),
  373. ArrayToVector(testCase.m_eigenpairs[(uniqueIndex + 1) % 3].m_vector),
  374. ArrayToVector(testCase.m_eigenpairs[(uniqueIndex + 2) % 3].m_vector),
  375. 1e-6
  376. );
  377. }
  378. INSTANTIATE_TEST_SUITE_P(
  379. All,
  380. ComputeEigenvector1RepeatedEigenvalueParams,
  381. ::testing::ValuesIn(testCasesRepeatedEigenvalues)
  382. );
  383. class ComputeEigenvector2Params
  384. : public ::testing::TestWithParam<TestCase>
  385. {
  386. };
  387. TEST_P(ComputeEigenvector2Params, ComputeEigenvector2_CorrectResult)
  388. {
  389. const TestCase& testCase = GetParam();
  390. // The third eigenvector is simply computed as the cross-product of the first two.
  391. for (int i = 0; i < 3; ++i)
  392. {
  393. const VectorVariable knownOne = VectorVariable::CreateFromVector(
  394. ArrayToVector(testCase.m_eigenpairs[(i + 0) % 3].m_vector)
  395. );
  396. const VectorVariable knownTwo = VectorVariable::CreateFromVector(
  397. ArrayToVector(testCase.m_eigenpairs[(i + 1) % 3].m_vector)
  398. );
  399. const VectorVariable expected = VectorVariable::CreateFromVector(
  400. ArrayToVector(testCase.m_eigenpairs[(i + 2) % 3].m_vector)
  401. );
  402. // The computed eigenvectors must either be parallel or anti-parallel to the expected eigenvectors.
  403. ExpectParallelUnitVector(ComputeEigenvector2(knownOne, knownTwo).GetValues(), expected.GetValues(), 1e-6);
  404. ExpectParallelUnitVector(ComputeEigenvector2(knownTwo, knownOne).GetValues(), expected.GetValues(), 1e-6);
  405. }
  406. }
  407. INSTANTIATE_TEST_SUITE_P(Unique, ComputeEigenvector2Params, ::testing::ValuesIn(testCasesUniqueEigenvalues));
  408. INSTANTIATE_TEST_SUITE_P(Repeated, ComputeEigenvector2Params, ::testing::ValuesIn(testCasesRepeatedEigenvalues));
  409. class NonIterativeSymmetricEigensolver3x3UniqueEigenvalueParams
  410. : public ::testing::TestWithParam<TestCase>
  411. {
  412. };
  413. TEST_P(
  414. NonIterativeSymmetricEigensolver3x3UniqueEigenvalueParams,
  415. NonIterativeSymmetricEigensolver3x3_CorrectResultForUniqueEigenvalue
  416. )
  417. {
  418. const TestCase& testCase = GetParam();
  419. SolverResult<Real, 3> result = NonIterativeSymmetricEigensolver3x3(
  420. testCase.m_matrix.m_00,
  421. testCase.m_matrix.m_01,
  422. testCase.m_matrix.m_02,
  423. testCase.m_matrix.m_11,
  424. testCase.m_matrix.m_12,
  425. testCase.m_matrix.m_22
  426. );
  427. // Must return exactly three eigenpairs.
  428. EXPECT_EQ(result.m_eigenpairs.size(), 3);
  429. // For non-diagonal matrices the eigenvalues will be sorted from smallest to largest.
  430. for (int i = 0; i < 3; ++i)
  431. {
  432. EXPECT_NEAR(result.m_eigenpairs[i].m_value, testCase.m_eigenpairs[i].m_value, 1e-6);
  433. ExpectParallelUnitVector(
  434. ArrayToVector(result.m_eigenpairs[i].m_vector), ArrayToVector(testCase.m_eigenpairs[i].m_vector), 1e-6
  435. );
  436. }
  437. ExpectRightHandedOrthogonalBasis(
  438. result.m_eigenpairs[0].m_vector, result.m_eigenpairs[1].m_vector, result.m_eigenpairs[2].m_vector, 1e-6
  439. );
  440. }
  441. INSTANTIATE_TEST_SUITE_P(
  442. All,
  443. NonIterativeSymmetricEigensolver3x3UniqueEigenvalueParams,
  444. ::testing::ValuesIn(testCasesUniqueEigenvalues)
  445. );
  446. class NonIterativeSymmetricEigensolver3x3RepeatedEigenvalueParams
  447. : public ::testing::TestWithParam<TestCase>
  448. {
  449. };
  450. TEST_P(
  451. NonIterativeSymmetricEigensolver3x3RepeatedEigenvalueParams,
  452. NonIterativeSymmetricEigensolver3x3_CorrectResultForRepeatedEigenvalue
  453. )
  454. {
  455. const TestCase& testCase = GetParam();
  456. // Get the index of the eigenpair with the unique eigenvalue.
  457. int uniqueIndex = testCase.m_eigenpairs[0].m_value == testCase.m_eigenpairs[1].m_value ? 2 : 0;
  458. SolverResult<Real, 3> result = NonIterativeSymmetricEigensolver3x3(
  459. testCase.m_matrix.m_00,
  460. testCase.m_matrix.m_01,
  461. testCase.m_matrix.m_02,
  462. testCase.m_matrix.m_11,
  463. testCase.m_matrix.m_12,
  464. testCase.m_matrix.m_22
  465. );
  466. // Must return exactly three eigenpairs.
  467. EXPECT_EQ(result.m_eigenpairs.size(), 3);
  468. // For non-diagonal matrices the eigenvalues will be sorted from smallest to largest.
  469. EXPECT_NEAR(result.m_eigenpairs[0].m_value, testCase.m_eigenpairs[0].m_value, 1e-6);
  470. EXPECT_NEAR(result.m_eigenpairs[1].m_value, testCase.m_eigenpairs[1].m_value, 1e-6);
  471. EXPECT_NEAR(result.m_eigenpairs[2].m_value, testCase.m_eigenpairs[2].m_value, 1e-6);
  472. for (int i = 0; i < 3; ++i)
  473. {
  474. if (i == uniqueIndex)
  475. {
  476. ExpectParallelUnitVector(
  477. ArrayToVector(result.m_eigenpairs[i].m_vector),
  478. ArrayToVector(testCase.m_eigenpairs[i].m_vector),
  479. 1e-6
  480. );
  481. }
  482. else
  483. {
  484. ExpectLinearlyDependentUnitVector(
  485. ArrayToVector(result.m_eigenpairs[i].m_vector),
  486. ArrayToVector(testCase.m_eigenpairs[(uniqueIndex + 1) % 3].m_vector),
  487. ArrayToVector(testCase.m_eigenpairs[(uniqueIndex + 2) % 3].m_vector),
  488. 1e-6
  489. );
  490. }
  491. }
  492. ExpectRightHandedOrthogonalBasis(
  493. result.m_eigenpairs[0].m_vector, result.m_eigenpairs[1].m_vector, result.m_eigenpairs[2].m_vector, 1e-6
  494. );
  495. }
  496. INSTANTIATE_TEST_SUITE_P(
  497. All,
  498. NonIterativeSymmetricEigensolver3x3RepeatedEigenvalueParams,
  499. ::testing::ValuesIn(testCasesRepeatedEigenvalues)
  500. );
  501. class NonIterativeSymmetricEigensolver3x3DiagonalMatrixParams
  502. : public ::testing::TestWithParam<AZStd::array<double, 3>>
  503. {
  504. };
  505. TEST_P(
  506. NonIterativeSymmetricEigensolver3x3DiagonalMatrixParams,
  507. NonIterativeSymmetricEigensolver3x3_CorrectResultForDiagonalMatrix
  508. )
  509. {
  510. const AZStd::array<double, 3>& matrixDiagonal = GetParam();
  511. SolverResult<Real, 3> result = NonIterativeSymmetricEigensolver3x3(
  512. matrixDiagonal[0], 0.0, 0.0, matrixDiagonal[1], 0.0, matrixDiagonal[2]
  513. );
  514. // Must return exactly three eigenpairs.
  515. EXPECT_EQ(result.m_eigenpairs.size(), 3);
  516. // This is a special case in which the eigenvectors are the standard Cartesian basis vectors (returned in the
  517. // same order). This test also covers the zero matrix.
  518. EXPECT_NEAR(result.m_eigenpairs[0].m_value, matrixDiagonal[0], 1e-6);
  519. ExpectParallelUnitVector(ArrayToVector(result.m_eigenpairs[0].m_vector), { 1.0, 0.0, 0.0 }, 1e-6);
  520. EXPECT_NEAR(result.m_eigenpairs[1].m_value, matrixDiagonal[1], 1e-6);
  521. ExpectParallelUnitVector(ArrayToVector(result.m_eigenpairs[1].m_vector), { 0.0, 1.0, 0.0 }, 1e-6);
  522. EXPECT_NEAR(result.m_eigenpairs[2].m_value, matrixDiagonal[2], 1e-6);
  523. ExpectParallelUnitVector(ArrayToVector(result.m_eigenpairs[2].m_vector), { 0.0, 0.0, 1.0 }, 1e-6);
  524. }
  525. INSTANTIATE_TEST_SUITE_P(
  526. All,
  527. NonIterativeSymmetricEigensolver3x3DiagonalMatrixParams,
  528. ::testing::Values(
  529. AZStd::array<double, 3>({ 1.0, 1.0, 1.0 }),
  530. AZStd::array<double, 3>({ 1.0, 0.0, -1.0 }),
  531. AZStd::array<double, 3>({ 3.0, -8.0, 5.0 }),
  532. AZStd::array<double, 3>({ 0.0, 0.0, 0.0 })
  533. )
  534. );
  535. } // namespace NumericalMethods::Eigenanalysis