gtx_pca.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. #define GLM_ENABLE_EXPERIMENTAL
  2. #include <glm/glm.hpp>
  3. #include <glm/gtx/pca.hpp>
  4. #include <glm/gtc/epsilon.hpp>
  5. #include <glm/gtx/string_cast.hpp>
  6. #include <cstdio>
  7. #include <vector>
  8. #if GLM_HAS_CXX11_STL == 1
  9. #include <random>
  10. #endif
  11. #if GLM_COMPILER & GLM_COMPILER_CLANG
  12. # pragma clang diagnostic push
  13. # pragma clang diagnostic ignored "-Wsign-conversion"
  14. #endif
  15. template<typename T>
  16. T myEpsilon();
  17. template<>
  18. GLM_INLINE constexpr float myEpsilon<float>() { return 0.00001f; }
  19. template<>
  20. GLM_INLINE constexpr double myEpsilon<double>() { return 0.000001; }
  21. template<glm::length_t D, typename T, glm::qualifier Q>
  22. static bool vectorEpsilonEqual(glm::vec<D, T, Q> const& a, glm::vec<D, T, Q> const& b, T epsilon)
  23. {
  24. for (int c = 0; c < D; ++c)
  25. if (!glm::epsilonEqual(a[c], b[c], epsilon))
  26. {
  27. fprintf(stderr, "failing vectorEpsilonEqual: [%d] %lf != %lf (~%lf)\n",
  28. c,
  29. static_cast<double>(a[c]),
  30. static_cast<double>(b[c]),
  31. static_cast<double>(epsilon)
  32. );
  33. return false;
  34. }
  35. return true;
  36. }
  37. template<glm::length_t D, typename T, glm::qualifier Q>
  38. static bool matrixEpsilonEqual(glm::mat<D, D, T, Q> const& a, glm::mat<D, D, T, Q> const& b, T epsilon)
  39. {
  40. for (int c = 0; c < D; ++c)
  41. for (int r = 0; r < D; ++r)
  42. if (!glm::epsilonEqual(a[c][r], b[c][r], epsilon))
  43. {
  44. fprintf(stderr, "failing vectorEpsilonEqual: [%d][%d] %lf != %lf (~%lf)\n",
  45. c, r,
  46. static_cast<double>(a[c][r]),
  47. static_cast<double>(b[c][r]),
  48. static_cast<double>(epsilon)
  49. );
  50. return false;
  51. }
  52. return true;
  53. }
  54. template<typename T>
  55. GLM_INLINE bool sameSign(T const& a, T const& b)
  56. {
  57. return ((a >= 0) && (b >= 0)) || ((a < 0) && (b < 0));
  58. }
  59. template<typename T>
  60. static T failReport(T line)
  61. {
  62. fprintf(stderr, "Failed in line %d\n", static_cast<int>(line));
  63. return line;
  64. }
  65. // Test data: 1AGA 'agarose double helix'
  66. // https://www.rcsb.org/structure/1aga
  67. // The fourth coordinate is randomized
  68. namespace agarose
  69. {
  70. // Fills `outTestData` with hard-coded atom positions from 1AGA
  71. // The fourth coordinate is randomized
  72. template<typename vec>
  73. static void fillTestData(std::vector<vec>& outTestData)
  74. {
  75. // x,y,z coordinates copied from RCSB PDB file of 1AGA
  76. // w coordinate randomized with standard normal distribution
  77. static const double _1aga[] = {
  78. 3.219, -0.637, 19.462, 2.286,
  79. 4.519, 0.024, 18.980, -0.828,
  80. 4.163, 1.425, 18.481, -0.810,
  81. 3.190, 1.341, 17.330, -0.170,
  82. 1.962, 0.991, 18.165, 0.816,
  83. 2.093, 1.952, 19.331, 0.276,
  84. 5.119, -0.701, 17.908, -0.490,
  85. 3.517, 2.147, 19.514, -0.207,
  86. 2.970, 2.609, 16.719, 0.552,
  87. 2.107, -0.398, 18.564, 0.403,
  88. 2.847, 2.618, 15.335, 0.315,
  89. 1.457, 3.124, 14.979, 0.683,
  90. 1.316, 3.291, 13.473, 0.446,
  91. 2.447, 4.155, 12.931, 1.324,
  92. 3.795, 3.614, 13.394, 0.112,
  93. 4.956, 4.494, 12.982, 0.253,
  94. 0.483, 2.217, 15.479, 1.316,
  95. 0.021, 3.962, 13.166, 1.522,
  96. 2.311, 5.497, 13.395, 0.248,
  97. 3.830, 3.522, 14.827, 0.591,
  98. 5.150, 4.461, 11.576, 0.635,
  99. -1.057, 3.106, 13.132, 0.191,
  100. -2.280, 3.902, 12.650, 1.135,
  101. -3.316, 2.893, 12.151, 0.794,
  102. -2.756, 2.092, 11.000, 0.720,
  103. -1.839, 1.204, 11.835, -1.172,
  104. -2.737, 0.837, 13.001, -0.313,
  105. -1.952, 4.784, 11.578, 2.082,
  106. -3.617, 1.972, 13.184, 0.653,
  107. -3.744, 1.267, 10.389, -0.413,
  108. -0.709, 2.024, 12.234, -1.747,
  109. -3.690, 1.156, 9.005, -1.275,
  110. -3.434, -0.300, 8.649, 0.441,
  111. -3.508, -0.506, 7.143, 0.237,
  112. -4.822, 0.042, 6.601, -2.856,
  113. -5.027, 1.480, 7.064, 0.985,
  114. -6.370, 2.045, 6.652, 0.915,
  115. -2.162, -0.690, 9.149, 1.100,
  116. -3.442, -1.963, 6.836, -0.081,
  117. -5.916, -0.747, 7.065, -2.345,
  118. -4.965, 1.556, 8.497, 0.504,
  119. -6.439, 2.230, 5.246, 1.451,
  120. -2.161, -2.469, 6.802, -1.171,
  121. -2.239, -3.925, 6.320, -1.434,
  122. -0.847, -4.318, 5.821, 0.098,
  123. -0.434, -3.433, 4.670, -1.446,
  124. -0.123, -2.195, 5.505, 0.182,
  125. 0.644, -2.789, 6.671, 0.865,
  126. -3.167, -4.083, 5.248, -0.098,
  127. 0.101, -4.119, 6.854, -0.001,
  128. 0.775, -3.876, 4.059, 1.061,
  129. -1.398, -1.625, 5.904, 0.230,
  130. 0.844, -3.774, 2.675, 1.313,
  131. 1.977, -2.824, 2.319, -0.112,
  132. 2.192, -2.785, 0.813, -0.981,
  133. 2.375, -4.197, 0.271, -0.355,
  134. 1.232, -5.093, 0.734, 0.632,
  135. 1.414, -6.539, 0.322, 0.576,
  136. 1.678, -1.527, 2.819, -1.187,
  137. 3.421, -1.999, 0.496, -1.770,
  138. 3.605, -4.750, 0.735, 1.099,
  139. 1.135, -5.078, 2.167, 0.854,
  140. 1.289, -6.691, -1.084, -0.487,
  141. -1.057, 3.106, 22.602, -1.297,
  142. -2.280, 3.902, 22.120, 0.376,
  143. -3.316, 2.893, 21.621, 0.932,
  144. -2.756, 2.092, 20.470, 1.680,
  145. -1.839, 1.204, 21.305, 0.615,
  146. -2.737, 0.837, 22.471, 0.899,
  147. -1.952, 4.784, 21.048, -0.521,
  148. -3.617, 1.972, 22.654, 0.133,
  149. -3.744, 1.267, 19.859, 0.081,
  150. -0.709, 2.024, 21.704, 1.420,
  151. -3.690, 1.156, 18.475, -0.850,
  152. -3.434, -0.300, 18.119, -0.249,
  153. -3.508, -0.506, 16.613, 1.434,
  154. -4.822, 0.042, 16.071, -2.466,
  155. -5.027, 1.480, 16.534, -1.045,
  156. -6.370, 2.045, 16.122, 1.707,
  157. -2.162, -0.690, 18.619, -2.023,
  158. -3.442, -1.963, 16.336, -0.304,
  159. -5.916, -0.747, 16.535, 0.979,
  160. -4.965, 1.556, 17.967, -1.165,
  161. -6.439, 2.230, 14.716, 0.929,
  162. -2.161, -2.469, 16.302, -0.234,
  163. -2.239, -3.925, 15.820, -0.228,
  164. -0.847, -4.318, 15.321, 1.844,
  165. -0.434, -3.433, 14.170, 1.132,
  166. -0.123, -2.195, 15.005, 0.211,
  167. 0.644, -2.789, 16.171, -0.632,
  168. -3.167, -4.083, 14.748, -0.519,
  169. 0.101, -4.119, 16.354, 0.173,
  170. 0.775, -3.876, 13.559, 1.243,
  171. -1.398, -1.625, 15.404, -0.187,
  172. 0.844, -3.774, 12.175, -1.332,
  173. 1.977, -2.824, 11.819, -1.616,
  174. 2.192, -2.785, 10.313, 1.320,
  175. 2.375, -4.197, 9.771, 0.237,
  176. 1.232, -5.093, 10.234, 0.851,
  177. 1.414, -6.539, 9.822, 1.816,
  178. 1.678, -1.527, 12.319, -1.657,
  179. 3.421, -1.999, 10.036, 1.559,
  180. 3.605, -4.750, 10.235, 0.831,
  181. 1.135, -5.078, 11.667, 0.060,
  182. 1.289, -6.691, 8.416, 1.066,
  183. 3.219, -0.637, 10.002, 2.111,
  184. 4.519, 0.024, 9.520, -0.874,
  185. 4.163, 1.425, 9.021, -1.012,
  186. 3.190, 1.341, 7.870, -0.250,
  187. 1.962, 0.991, 8.705, -1.359,
  188. 2.093, 1.952, 9.871, -0.126,
  189. 5.119, -0.701, 8.448, 0.995,
  190. 3.517, 2.147, 10.054, 0.941,
  191. 2.970, 2.609, 7.259, -0.562,
  192. 2.107, -0.398, 9.104, -0.038,
  193. 2.847, 2.618, 5.875, 0.398,
  194. 1.457, 3.124, 5.519, 0.481,
  195. 1.316, 3.291, 4.013, -0.187,
  196. 2.447, 4.155, 3.471, -0.429,
  197. 3.795, 3.614, 3.934, -0.432,
  198. 4.956, 4.494, 3.522, -0.788,
  199. 0.483, 2.217, 6.019, -0.923,
  200. 0.021, 3.962, 3.636, -0.316,
  201. 2.311, 5.497, 3.935, -1.917,
  202. 3.830, 3.522, 5.367, -0.302,
  203. 5.150, 4.461, 2.116, -1.615
  204. };
  205. static const glm::length_t _1agaSize = sizeof(_1aga) / (4 * sizeof(double));
  206. outTestData.resize(_1agaSize);
  207. for(glm::length_t i = 0; i < _1agaSize; ++i)
  208. for(glm::length_t d = 0; d < static_cast<glm::length_t>(vec::length()); ++d)
  209. outTestData[i][d] = static_cast<typename vec::value_type>(_1aga[i * 4 + d]);
  210. }
  211. // All reference values computed separately using symbolic precision
  212. // https://github.com/sgrottel/exp-pca-precision
  213. // This applies to all functions named: `agarose::expected*()`
  214. GLM_INLINE glm::dmat4 const& expectedCovarData()
  215. {
  216. static const glm::dmat4 covar4x4d(
  217. 9.62434068027210898322, -0.00006657369614512471, -4.29321376568405099761, 0.01879374187452758846,
  218. -0.00006657369614512471, 9.62443937868480681175, 5.35113872637944076871, -0.11569259145880574080,
  219. -4.29321376568405099761, 5.35113872637944076871, 35.62848549634668415820, 0.90874239254220201545,
  220. 0.01879374187452758846, -0.11569259145880574080, 0.90874239254220201545, 1.09705971856890904803
  221. );
  222. return covar4x4d;
  223. }
  224. template<glm::length_t D>
  225. GLM_INLINE glm::vec<D, double, glm::defaultp> const& expectedEigenvalues();
  226. template<>
  227. GLM_INLINE glm::dvec2 const& expectedEigenvalues<2>()
  228. {
  229. static const glm::dvec2 evals2(
  230. 9.62447289926297399961763301774251330057894539467032275382255,
  231. 9.62430715969394210015560961264297422776572580714373620309355
  232. );
  233. return evals2;
  234. }
  235. template<>
  236. GLM_INLINE glm::dvec3 const& expectedEigenvalues<3>()
  237. {
  238. static const glm::dvec3 evals3(
  239. 37.3274494274683425233695502581182052836449738530676689472257,
  240. 9.62431434161498823505729817436585077939509766554969096873168,
  241. 7.92550178622027216422369326567668971675332732240052872097887
  242. );
  243. return evals3;
  244. }
  245. template<>
  246. GLM_INLINE glm::dvec4 const& expectedEigenvalues<4>()
  247. {
  248. static const glm::dvec4 evals4(
  249. 37.3477389918792213596879452204499702406947817221901007885630,
  250. 9.62470688921105696017807313860277172063600080413412567999700,
  251. 7.94017075281634999342344275928070533134615133171969063657713,
  252. 1.06170863996588365446060186982477896078741484440002343404155
  253. );
  254. return evals4;
  255. }
  256. template<glm::length_t D>
  257. GLM_INLINE glm::mat<D, D, double, glm::defaultp> const& expectedEigenvectors();
  258. template<>
  259. GLM_INLINE glm::dmat2 const& expectedEigenvectors<2>()
  260. {
  261. static const glm::dmat2 evecs2(
  262. glm::dvec2(
  263. -0.503510847492551904906870957742619139443409162857537237123308,
  264. 1
  265. ),
  266. glm::dvec2(
  267. 1.98605453086051402895741763848787613048533838388005162794043,
  268. 1
  269. )
  270. );
  271. return evecs2;
  272. }
  273. template<>
  274. GLM_INLINE glm::dmat3 const& expectedEigenvectors<3>()
  275. {
  276. static const glm::dmat3 evecs3(
  277. glm::dvec3(
  278. -0.154972738414395866005286433008304444294405085038689821864654,
  279. 0.193161285869815165989799191097521722568079378840201629578695,
  280. 1
  281. ),
  282. glm::dvec3(
  283. -158565.112775416943154745839952575022429933119522746586149868,
  284. -127221.506282351944358932458687410410814983610301927832439675,
  285. 1
  286. ),
  287. glm::dvec3(
  288. 2.52702248596556806145700361724323960543858113426446460406536,
  289. -3.14959802931313870497377546974185300816008580801457419079412,
  290. 1
  291. )
  292. );
  293. return evecs3;
  294. }
  295. template<>
  296. GLM_INLINE glm::dmat4 const& expectedEigenvectors<4>()
  297. {
  298. static const glm::dmat4 evecs4(
  299. glm::dvec4(
  300. -6.35322390281037045217295803597357821705371650876122113027264,
  301. 7.91546394153385394517767054617789939529794642646629201212056,
  302. 41.0301543819240679808549819457450130787045236815736490549663,
  303. 1
  304. ),
  305. glm::dvec4(
  306. -114.622418941087829756565311692197154422302604224781253861297,
  307. -92.2070185807065289900871215218752013659402949497379896153118,
  308. 0.0155846091025912430932734548933329458404665760587569100867246,
  309. 1
  310. ),
  311. glm::dvec4(
  312. 13.1771887761559019483954743159026938257325190511642952175789,
  313. -16.3688257459634877666638419310116970616615816436949741766895,
  314. 5.17386502341472097227408249233288958059579189051394773143190,
  315. 1
  316. ),
  317. glm::dvec4(
  318. -0.0192777078948229800494895064532553117703859768210647632969276,
  319. 0.0348034950916108873629241563077465542944938906271231198634442,
  320. -0.0340715609308469289267379681032545422644143611273049912226126,
  321. 1
  322. )
  323. );
  324. return evecs4;
  325. }
  326. } // namespace agarose
  327. // Compute center of gravity
  328. template<typename vec>
  329. static vec computeCenter(const std::vector<vec>& testData)
  330. {
  331. double c[4];
  332. std::fill(c, c + vec::length(), 0.0);
  333. typename std::vector<vec>::const_iterator e = testData.end();
  334. for(typename std::vector<vec>::const_iterator i = testData.begin(); i != e; ++i)
  335. for(glm::length_t d = 0; d < static_cast<glm::length_t>(vec::length()); ++d)
  336. c[d] += static_cast<double>((*i)[d]);
  337. vec cVec(0);
  338. for(glm::length_t d = 0; d < static_cast<glm::length_t>(vec::length()); ++d)
  339. cVec[d] = static_cast<typename vec::value_type>(c[d] / static_cast<double>(testData.size()));
  340. return cVec;
  341. }
  342. // Test sorting of Eigenvalue&Eigenvector lists. Use exhaustive search.
  343. template<glm::length_t D, typename T, glm::qualifier Q>
  344. static int testEigenvalueSort()
  345. {
  346. // Test input data: four arbitrary values
  347. static const glm::vec<D, T, Q> refVal(
  348. glm::vec<4, T, Q>(
  349. 10, 8, 6, 4
  350. )
  351. );
  352. // Test input data: four arbitrary vectors, which can be matched to the above values
  353. static const glm::mat<D, D, T, Q> refVec(
  354. glm::mat<4, 4, T, Q>(
  355. 10, 20, 5, 40,
  356. 8, 16, 4, 32,
  357. 6, 12, 3, 24,
  358. 4, 8, 2, 16
  359. )
  360. );
  361. // Permutations of test input data for exhaustive check, based on `D` (1 <= D <= 4)
  362. static const int permutationCount[] = {
  363. 0,
  364. 1,
  365. 2,
  366. 6,
  367. 24
  368. };
  369. // The permutations t perform, based on `D` (1 <= D <= 4)
  370. static const glm::ivec4 permutation[] = {
  371. glm::ivec4(0, 1, 2, 3),
  372. glm::ivec4(1, 0, 2, 3), // last for D = 2
  373. glm::ivec4(0, 2, 1, 3),
  374. glm::ivec4(1, 2, 0, 3),
  375. glm::ivec4(2, 0, 1, 3),
  376. glm::ivec4(2, 1, 0, 3), // last for D = 3
  377. glm::ivec4(0, 1, 3, 2),
  378. glm::ivec4(1, 0, 3, 2),
  379. glm::ivec4(0, 2, 3, 1),
  380. glm::ivec4(1, 2, 3, 0),
  381. glm::ivec4(2, 0, 3, 1),
  382. glm::ivec4(2, 1, 3, 0),
  383. glm::ivec4(0, 3, 1, 2),
  384. glm::ivec4(1, 3, 0, 2),
  385. glm::ivec4(0, 3, 2, 1),
  386. glm::ivec4(1, 3, 2, 0),
  387. glm::ivec4(2, 3, 0, 1),
  388. glm::ivec4(2, 3, 1, 0),
  389. glm::ivec4(3, 0, 1, 2),
  390. glm::ivec4(3, 1, 0, 2),
  391. glm::ivec4(3, 0, 2, 1),
  392. glm::ivec4(3, 1, 2, 0),
  393. glm::ivec4(3, 2, 0, 1),
  394. glm::ivec4(3, 2, 1, 0) // last for D = 4
  395. };
  396. // initial sanity check
  397. if(!vectorEpsilonEqual(refVal, refVal, myEpsilon<T>()))
  398. return failReport(__LINE__);
  399. if(!matrixEpsilonEqual(refVec, refVec, myEpsilon<T>()))
  400. return failReport(__LINE__);
  401. // Exhaustive search through all permutations
  402. for(int p = 0; p < permutationCount[D]; ++p)
  403. {
  404. glm::vec<D, T, Q> testVal;
  405. glm::mat<D, D, T, Q> testVec;
  406. for(int i = 0; i < D; ++i)
  407. {
  408. testVal[i] = refVal[permutation[p][i]];
  409. testVec[i] = refVec[permutation[p][i]];
  410. }
  411. glm::sortEigenvalues(testVal, testVec);
  412. if (!vectorEpsilonEqual(testVal, refVal, myEpsilon<T>()))
  413. return failReport(__LINE__);
  414. if (!matrixEpsilonEqual(testVec, refVec, myEpsilon<T>()))
  415. return failReport(__LINE__);
  416. }
  417. return 0;
  418. }
  419. // Test covariance matrix creation functions
  420. template<glm::length_t D, typename T, glm::qualifier Q>
  421. static int testCovar(
  422. #if GLM_HAS_CXX11_STL == 1
  423. glm::length_t dataSize, unsigned int randomEngineSeed
  424. #else // GLM_HAS_CXX11_STL == 1
  425. glm::length_t, unsigned int
  426. #endif // GLM_HAS_CXX11_STL == 1
  427. )
  428. {
  429. typedef glm::vec<D, T, Q> vec;
  430. typedef glm::mat<D, D, T, Q> mat;
  431. // #1: test expected result with fixed data set
  432. std::vector<vec> testData;
  433. agarose::fillTestData(testData);
  434. // compute center of gravity
  435. vec center = computeCenter(testData);
  436. mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
  437. if(!matrixEpsilonEqual(covarMat, mat(agarose::expectedCovarData()), myEpsilon<T>()))
  438. {
  439. fprintf(stderr, "Reconstructed covarMat:\n%s\n", glm::to_string(covarMat).c_str());
  440. return failReport(__LINE__);
  441. }
  442. // #2: test function variant consistency with random data
  443. #if GLM_HAS_CXX11_STL == 1
  444. std::default_random_engine rndEng(randomEngineSeed);
  445. std::normal_distribution<T> normalDist;
  446. testData.resize(dataSize);
  447. // some common offset of all data
  448. T offset[D];
  449. for(glm::length_t d = 0; d < D; ++d)
  450. offset[d] = normalDist(rndEng);
  451. // init data
  452. for(glm::length_t i = 0; i < dataSize; ++i)
  453. for(glm::length_t d = 0; d < D; ++d)
  454. testData[i][d] = offset[d] + normalDist(rndEng);
  455. center = computeCenter(testData);
  456. std::vector<vec> centeredTestData;
  457. centeredTestData.reserve(testData.size());
  458. typename std::vector<vec>::const_iterator e = testData.end();
  459. for(typename std::vector<vec>::const_iterator i = testData.begin(); i != e; ++i)
  460. centeredTestData.push_back((*i) - center);
  461. mat c1 = glm::computeCovarianceMatrix(centeredTestData.data(), centeredTestData.size());
  462. mat c2 = glm::computeCovarianceMatrix<D, T, Q>(centeredTestData.begin(), centeredTestData.end());
  463. mat c3 = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
  464. mat c4 = glm::computeCovarianceMatrix<D, T, Q>(testData.rbegin(), testData.rend(), center);
  465. if(!matrixEpsilonEqual(c1, c2, myEpsilon<T>()))
  466. return failReport(__LINE__);
  467. if(!matrixEpsilonEqual(c1, c3, myEpsilon<T>()))
  468. return failReport(__LINE__);
  469. if(!matrixEpsilonEqual(c1, c4, myEpsilon<T>()))
  470. return failReport(__LINE__);
  471. #endif // GLM_HAS_CXX11_STL == 1
  472. return 0;
  473. }
  474. // Computes eigenvalues and eigenvectors from well-known covariance matrix
  475. template<glm::length_t D, typename T, glm::qualifier Q>
  476. static int testEigenvectors(T epsilon)
  477. {
  478. typedef glm::vec<D, T, Q> vec;
  479. typedef glm::mat<D, D, T, Q> mat;
  480. // test expected result with fixed data set
  481. std::vector<vec> testData;
  482. mat covarMat(agarose::expectedCovarData());
  483. vec eigenvalues;
  484. mat eigenvectors;
  485. unsigned int c = glm::findEigenvaluesSymReal(covarMat, eigenvalues, eigenvectors);
  486. if(c != D)
  487. return failReport(__LINE__);
  488. glm::sortEigenvalues(eigenvalues, eigenvectors);
  489. if (!vectorEpsilonEqual(eigenvalues, vec(agarose::expectedEigenvalues<D>()), epsilon))
  490. return failReport(__LINE__);
  491. for (int i = 0; i < D; ++i)
  492. {
  493. vec act = glm::normalize(eigenvectors[i]);
  494. vec exp = glm::normalize(agarose::expectedEigenvectors<D>()[i]);
  495. if (!sameSign(act[0], exp[0])) exp = -exp;
  496. if (!vectorEpsilonEqual(act, exp, epsilon))
  497. return failReport(__LINE__);
  498. }
  499. return 0;
  500. }
  501. // A simple small smoke test:
  502. // - a uniformly sampled block
  503. // - reconstruct main axes
  504. // - check order of eigenvalues equals order of extends of block in direction of main axes
  505. static int smokeTest()
  506. {
  507. using glm::vec3;
  508. using glm::mat3;
  509. std::vector<vec3> pts;
  510. pts.reserve(11 * 15 * 7);
  511. for(int x = -5; x <= 5; ++x)
  512. for(int y = -7; y <= 7; ++y)
  513. for(int z = -3; z <= 3; ++z)
  514. pts.push_back(vec3(x, y, z));
  515. mat3 covar = glm::computeCovarianceMatrix(pts.data(), pts.size());
  516. mat3 eVec;
  517. vec3 eVal;
  518. unsigned int eCnt = glm::findEigenvaluesSymReal(covar, eVal, eVec);
  519. if(eCnt != 3u)
  520. return failReport(__LINE__);
  521. // sort eVec by descending eVal
  522. if(eVal[0] < eVal[1])
  523. {
  524. std::swap(eVal[0], eVal[1]);
  525. std::swap(eVec[0], eVec[1]);
  526. }
  527. if(eVal[0] < eVal[2])
  528. {
  529. std::swap(eVal[0], eVal[2]);
  530. std::swap(eVec[0], eVec[2]);
  531. }
  532. if(eVal[1] < eVal[2])
  533. {
  534. std::swap(eVal[1], eVal[2]);
  535. std::swap(eVec[1], eVec[2]);
  536. }
  537. if(!vectorEpsilonEqual(glm::abs(eVec[0]), vec3(0, 1, 0), myEpsilon<float>()))
  538. return failReport(__LINE__);
  539. if(!vectorEpsilonEqual(glm::abs(eVec[1]), vec3(1, 0, 0), myEpsilon<float>()))
  540. return failReport(__LINE__);
  541. if(!vectorEpsilonEqual(glm::abs(eVec[2]), vec3(0, 0, 1), myEpsilon<float>()))
  542. return failReport(__LINE__);
  543. return 0;
  544. }
  545. #if GLM_HAS_CXX11_STL == 1
  546. static int rndTest(unsigned int randomEngineSeed)
  547. {
  548. std::default_random_engine rndEng(randomEngineSeed);
  549. std::normal_distribution<double> normalDist;
  550. // construct orthonormal system
  551. glm::dvec3 x(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  552. double l = glm::length(x);
  553. while(l < myEpsilon<double>())
  554. x = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  555. x = glm::normalize(x);
  556. glm::dvec3 y(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  557. l = glm::length(y);
  558. while(l < myEpsilon<double>())
  559. y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  560. while(glm::abs(glm::dot(x, y)) < myEpsilon<double>())
  561. {
  562. y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  563. while(l < myEpsilon<double>())
  564. y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  565. }
  566. y = glm::normalize(y);
  567. glm::dvec3 z = glm::normalize(glm::cross(x, y));
  568. y = glm::normalize(glm::cross(z, x));
  569. // generate input point data
  570. std::vector<glm::dvec3> ptData;
  571. static const int pattern[] = {
  572. 8, 0, 0,
  573. 4, 1, 2,
  574. 0, 2, 0,
  575. 0, 0, 4
  576. };
  577. glm::dvec3 offset(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng));
  578. for(int p = 0; p < 4; ++p)
  579. for(int xs = 1; xs >= -1; xs -= 2)
  580. for(int ys = 1; ys >= -1; ys -= 2)
  581. for(int zs = 1; zs >= -1; zs -= 2)
  582. ptData.push_back(
  583. offset
  584. + x * static_cast<double>(pattern[p * 3 + 0] * xs)
  585. + y * static_cast<double>(pattern[p * 3 + 1] * ys)
  586. + z * static_cast<double>(pattern[p * 3 + 2] * zs));
  587. // perform PCA:
  588. glm::dvec3 center = computeCenter(ptData);
  589. glm::dmat3 covarMat = glm::computeCovarianceMatrix(ptData.data(), ptData.size(), center);
  590. glm::dvec3 evals;
  591. glm::dmat3 evecs;
  592. unsigned int evcnt = glm::findEigenvaluesSymReal(covarMat, evals, evecs);
  593. if(evcnt != 3u)
  594. return failReport(__LINE__);
  595. glm::sortEigenvalues(evals, evecs);
  596. if (!sameSign(evecs[0][0], x[0])) evecs[0] = -evecs[0];
  597. if(!vectorEpsilonEqual(x, evecs[0], myEpsilon<double>()))
  598. return failReport(__LINE__);
  599. if (!sameSign(evecs[2][0], y[0])) evecs[2] = -evecs[2];
  600. if (!vectorEpsilonEqual(y, evecs[2], myEpsilon<double>()))
  601. return failReport(__LINE__);
  602. if (!sameSign(evecs[1][0], z[0])) evecs[1] = -evecs[1];
  603. if (!vectorEpsilonEqual(z, evecs[1], myEpsilon<double>()))
  604. return failReport(__LINE__);
  605. return 0;
  606. }
  607. #endif // GLM_HAS_CXX11_STL == 1
  608. int main()
  609. {
  610. int error(0);
  611. // A small smoke test to fail early with most problems
  612. if(smokeTest())
  613. return failReport(__LINE__);
  614. // test sorting utility.
  615. if(testEigenvalueSort<2, float, glm::defaultp>() != 0)
  616. error = failReport(__LINE__);
  617. if(testEigenvalueSort<2, double, glm::defaultp>() != 0)
  618. error = failReport(__LINE__);
  619. if(testEigenvalueSort<3, float, glm::defaultp>() != 0)
  620. error = failReport(__LINE__);
  621. if(testEigenvalueSort<3, double, glm::defaultp>() != 0)
  622. error = failReport(__LINE__);
  623. if(testEigenvalueSort<4, float, glm::defaultp>() != 0)
  624. error = failReport(__LINE__);
  625. if(testEigenvalueSort<4, double, glm::defaultp>() != 0)
  626. error = failReport(__LINE__);
  627. if (error != 0)
  628. return error;
  629. // Note: the random engine uses a fixed seed to create consistent and reproducible test data
  630. // test covariance matrix computation from different data sources
  631. if(testCovar<2, float, glm::defaultp>(100, 12345) != 0)
  632. error = failReport(__LINE__);
  633. if(testCovar<2, double, glm::defaultp>(100, 42) != 0)
  634. error = failReport(__LINE__);
  635. if(testCovar<3, float, glm::defaultp>(100, 2021) != 0)
  636. error = failReport(__LINE__);
  637. if(testCovar<3, double, glm::defaultp>(100, 815) != 0)
  638. error = failReport(__LINE__);
  639. if(testCovar<4, float, glm::defaultp>(100, 3141) != 0)
  640. error = failReport(__LINE__);
  641. if(testCovar<4, double, glm::defaultp>(100, 174) != 0)
  642. error = failReport(__LINE__);
  643. if (error != 0)
  644. return error;
  645. // test PCA eigen vector reconstruction
  646. // Expected epsilon precision evaluated separately:
  647. // https://github.com/sgrottel/exp-pca-precision
  648. if(testEigenvectors<2, float, glm::defaultp>(0.002f) != 0)
  649. error = failReport(__LINE__);
  650. if(testEigenvectors<2, double, glm::defaultp>(0.00000000001) != 0)
  651. error = failReport(__LINE__);
  652. if(testEigenvectors<3, float, glm::defaultp>(0.00001f) != 0)
  653. error = failReport(__LINE__);
  654. if(testEigenvectors<3, double, glm::defaultp>(0.0000000001) != 0)
  655. error = failReport(__LINE__);
  656. if(testEigenvectors<4, float, glm::defaultp>(0.0001f) != 0)
  657. error = failReport(__LINE__);
  658. if(testEigenvectors<4, double, glm::defaultp>(0.0000001) != 0)
  659. error = failReport(__LINE__);
  660. if(error != 0)
  661. return error;
  662. // Final tests with randomized data
  663. #if GLM_HAS_CXX11_STL == 1
  664. if(rndTest(12345) != 0)
  665. error = failReport(__LINE__);
  666. if(rndTest(42) != 0)
  667. error = failReport(__LINE__);
  668. if (error != 0)
  669. return error;
  670. #endif // GLM_HAS_CXX11_STL == 1
  671. return error;
  672. }
  673. #if GLM_COMPILER & GLM_COMPILER_CLANG
  674. # pragma clang diagnostic pop
  675. #endif