#define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include #if GLM_HAS_CXX11_STL == 1 #include #endif template T myEpsilon(); template<> GLM_INLINE GLM_CONSTEXPR float myEpsilon() { return 0.00001f; } template<> GLM_INLINE GLM_CONSTEXPR double myEpsilon() { return 0.000001; } template bool vectorEpsilonEqual(glm::vec const& a, glm::vec const& b, T epsilon) { for (int c = 0; c < D; ++c) if (!glm::epsilonEqual(a[c], b[c], epsilon)) { fprintf(stderr, "failing vectorEpsilonEqual: [%d] %lf != %lf (~%lf)\n", c, static_cast(a[c]), static_cast(b[c]), static_cast(epsilon) ); return false; } return true; } template bool matrixEpsilonEqual(glm::mat const& a, glm::mat const& b, T epsilon) { for (int c = 0; c < D; ++c) for (int r = 0; r < D; ++r) if (!glm::epsilonEqual(a[c][r], b[c][r], epsilon)) { fprintf(stderr, "failing vectorEpsilonEqual: [%d][%d] %lf != %lf (~%lf)\n", c, r, static_cast(a[c][r]), static_cast(b[c][r]), static_cast(epsilon) ); return false; } return true; } template GLM_INLINE bool sameSign(T const& a, T const& b) { return ((a >= 0) && (b >= 0)) || ((a < 0) && (b < 0)); } template T failReport(T line) { fprintf(stderr, "Failed in line %d\n", static_cast(line)); return line; } // Test data: 1AGA 'agarose double helix' // https://www.rcsb.org/structure/1aga // The fourth coordinate is randomized namespace agarose { // Fills `outTestData` with hard-coded atom positions from 1AGA // The fourth coordinate is randomized template void fillTestData(std::vector& outTestData) { // x,y,z coordinates copied from RCSB PDB file of 1AGA // w coordinate randomized with standard normal distribution static const double _1aga[] = { 3.219, -0.637, 19.462, 2.286, 4.519, 0.024, 18.980, -0.828, 4.163, 1.425, 18.481, -0.810, 3.190, 1.341, 17.330, -0.170, 1.962, 0.991, 18.165, 0.816, 2.093, 1.952, 19.331, 0.276, 5.119, -0.701, 17.908, -0.490, 3.517, 2.147, 19.514, -0.207, 2.970, 2.609, 16.719, 0.552, 2.107, -0.398, 18.564, 0.403, 2.847, 2.618, 15.335, 0.315, 1.457, 3.124, 14.979, 0.683, 1.316, 3.291, 13.473, 0.446, 2.447, 4.155, 12.931, 1.324, 3.795, 3.614, 13.394, 0.112, 4.956, 4.494, 12.982, 0.253, 0.483, 2.217, 15.479, 1.316, 0.021, 3.962, 13.166, 1.522, 2.311, 5.497, 13.395, 0.248, 3.830, 3.522, 14.827, 0.591, 5.150, 4.461, 11.576, 0.635, -1.057, 3.106, 13.132, 0.191, -2.280, 3.902, 12.650, 1.135, -3.316, 2.893, 12.151, 0.794, -2.756, 2.092, 11.000, 0.720, -1.839, 1.204, 11.835, -1.172, -2.737, 0.837, 13.001, -0.313, -1.952, 4.784, 11.578, 2.082, -3.617, 1.972, 13.184, 0.653, -3.744, 1.267, 10.389, -0.413, -0.709, 2.024, 12.234, -1.747, -3.690, 1.156, 9.005, -1.275, -3.434, -0.300, 8.649, 0.441, -3.508, -0.506, 7.143, 0.237, -4.822, 0.042, 6.601, -2.856, -5.027, 1.480, 7.064, 0.985, -6.370, 2.045, 6.652, 0.915, -2.162, -0.690, 9.149, 1.100, -3.442, -1.963, 6.836, -0.081, -5.916, -0.747, 7.065, -2.345, -4.965, 1.556, 8.497, 0.504, -6.439, 2.230, 5.246, 1.451, -2.161, -2.469, 6.802, -1.171, -2.239, -3.925, 6.320, -1.434, -0.847, -4.318, 5.821, 0.098, -0.434, -3.433, 4.670, -1.446, -0.123, -2.195, 5.505, 0.182, 0.644, -2.789, 6.671, 0.865, -3.167, -4.083, 5.248, -0.098, 0.101, -4.119, 6.854, -0.001, 0.775, -3.876, 4.059, 1.061, -1.398, -1.625, 5.904, 0.230, 0.844, -3.774, 2.675, 1.313, 1.977, -2.824, 2.319, -0.112, 2.192, -2.785, 0.813, -0.981, 2.375, -4.197, 0.271, -0.355, 1.232, -5.093, 0.734, 0.632, 1.414, -6.539, 0.322, 0.576, 1.678, -1.527, 2.819, -1.187, 3.421, -1.999, 0.496, -1.770, 3.605, -4.750, 0.735, 1.099, 1.135, -5.078, 2.167, 0.854, 1.289, -6.691, -1.084, -0.487, -1.057, 3.106, 22.602, -1.297, -2.280, 3.902, 22.120, 0.376, -3.316, 2.893, 21.621, 0.932, -2.756, 2.092, 20.470, 1.680, -1.839, 1.204, 21.305, 0.615, -2.737, 0.837, 22.471, 0.899, -1.952, 4.784, 21.048, -0.521, -3.617, 1.972, 22.654, 0.133, -3.744, 1.267, 19.859, 0.081, -0.709, 2.024, 21.704, 1.420, -3.690, 1.156, 18.475, -0.850, -3.434, -0.300, 18.119, -0.249, -3.508, -0.506, 16.613, 1.434, -4.822, 0.042, 16.071, -2.466, -5.027, 1.480, 16.534, -1.045, -6.370, 2.045, 16.122, 1.707, -2.162, -0.690, 18.619, -2.023, -3.442, -1.963, 16.336, -0.304, -5.916, -0.747, 16.535, 0.979, -4.965, 1.556, 17.967, -1.165, -6.439, 2.230, 14.716, 0.929, -2.161, -2.469, 16.302, -0.234, -2.239, -3.925, 15.820, -0.228, -0.847, -4.318, 15.321, 1.844, -0.434, -3.433, 14.170, 1.132, -0.123, -2.195, 15.005, 0.211, 0.644, -2.789, 16.171, -0.632, -3.167, -4.083, 14.748, -0.519, 0.101, -4.119, 16.354, 0.173, 0.775, -3.876, 13.559, 1.243, -1.398, -1.625, 15.404, -0.187, 0.844, -3.774, 12.175, -1.332, 1.977, -2.824, 11.819, -1.616, 2.192, -2.785, 10.313, 1.320, 2.375, -4.197, 9.771, 0.237, 1.232, -5.093, 10.234, 0.851, 1.414, -6.539, 9.822, 1.816, 1.678, -1.527, 12.319, -1.657, 3.421, -1.999, 10.036, 1.559, 3.605, -4.750, 10.235, 0.831, 1.135, -5.078, 11.667, 0.060, 1.289, -6.691, 8.416, 1.066, 3.219, -0.637, 10.002, 2.111, 4.519, 0.024, 9.520, -0.874, 4.163, 1.425, 9.021, -1.012, 3.190, 1.341, 7.870, -0.250, 1.962, 0.991, 8.705, -1.359, 2.093, 1.952, 9.871, -0.126, 5.119, -0.701, 8.448, 0.995, 3.517, 2.147, 10.054, 0.941, 2.970, 2.609, 7.259, -0.562, 2.107, -0.398, 9.104, -0.038, 2.847, 2.618, 5.875, 0.398, 1.457, 3.124, 5.519, 0.481, 1.316, 3.291, 4.013, -0.187, 2.447, 4.155, 3.471, -0.429, 3.795, 3.614, 3.934, -0.432, 4.956, 4.494, 3.522, -0.788, 0.483, 2.217, 6.019, -0.923, 0.021, 3.962, 3.636, -0.316, 2.311, 5.497, 3.935, -1.917, 3.830, 3.522, 5.367, -0.302, 5.150, 4.461, 2.116, -1.615 }; static const glm::length_t _1agaSize = sizeof(_1aga) / (4 * sizeof(double)); outTestData.resize(_1agaSize); for(glm::length_t i = 0; i < _1agaSize; ++i) for(glm::length_t d = 0; d < static_cast(vec::length()); ++d) outTestData[i][d] = static_cast(_1aga[i * 4 + d]); } // All reference values computed separately using symbolic precision // https://github.com/sgrottel/exp-pca-precision // This applies to all functions named: `agarose::expected*()` GLM_INLINE glm::dmat4 const& expectedCovarData() { static const glm::dmat4 covar4x4d( 9.62434068027210898322, -0.00006657369614512471, -4.29321376568405099761, 0.01879374187452758846, -0.00006657369614512471, 9.62443937868480681175, 5.35113872637944076871, -0.11569259145880574080, -4.29321376568405099761, 5.35113872637944076871, 35.62848549634668415820, 0.90874239254220201545, 0.01879374187452758846, -0.11569259145880574080, 0.90874239254220201545, 1.09705971856890904803 ); return covar4x4d; } template GLM_INLINE glm::vec const& expectedEigenvalues(); template<> GLM_INLINE glm::dvec2 const& expectedEigenvalues<2>() { static const glm::dvec2 evals2( 9.62447289926297399961763301774251330057894539467032275382255, 9.62430715969394210015560961264297422776572580714373620309355 ); return evals2; } template<> GLM_INLINE glm::dvec3 const& expectedEigenvalues<3>() { static const glm::dvec3 evals3( 37.3274494274683425233695502581182052836449738530676689472257, 9.62431434161498823505729817436585077939509766554969096873168, 7.92550178622027216422369326567668971675332732240052872097887 ); return evals3; } template<> GLM_INLINE glm::dvec4 const& expectedEigenvalues<4>() { static const glm::dvec4 evals4( 37.3477389918792213596879452204499702406947817221901007885630, 9.62470688921105696017807313860277172063600080413412567999700, 7.94017075281634999342344275928070533134615133171969063657713, 1.06170863996588365446060186982477896078741484440002343404155 ); return evals4; } template GLM_INLINE glm::mat const& expectedEigenvectors(); template<> GLM_INLINE glm::dmat2 const& expectedEigenvectors<2>() { static const glm::dmat2 evecs2( glm::dvec2( -0.503510847492551904906870957742619139443409162857537237123308, 1 ), glm::dvec2( 1.98605453086051402895741763848787613048533838388005162794043, 1 ) ); return evecs2; } template<> GLM_INLINE glm::dmat3 const& expectedEigenvectors<3>() { static const glm::dmat3 evecs3( glm::dvec3( -0.154972738414395866005286433008304444294405085038689821864654, 0.193161285869815165989799191097521722568079378840201629578695, 1 ), glm::dvec3( -158565.112775416943154745839952575022429933119522746586149868, -127221.506282351944358932458687410410814983610301927832439675, 1 ), glm::dvec3( 2.52702248596556806145700361724323960543858113426446460406536, -3.14959802931313870497377546974185300816008580801457419079412, 1 ) ); return evecs3; } template<> GLM_INLINE glm::dmat4 const& expectedEigenvectors<4>() { static const glm::dmat4 evecs4( glm::dvec4( -6.35322390281037045217295803597357821705371650876122113027264, 7.91546394153385394517767054617789939529794642646629201212056, 41.0301543819240679808549819457450130787045236815736490549663, 1 ), glm::dvec4( -114.622418941087829756565311692197154422302604224781253861297, -92.2070185807065289900871215218752013659402949497379896153118, 0.0155846091025912430932734548933329458404665760587569100867246, 1 ), glm::dvec4( 13.1771887761559019483954743159026938257325190511642952175789, -16.3688257459634877666638419310116970616615816436949741766895, 5.17386502341472097227408249233288958059579189051394773143190, 1 ), glm::dvec4( -0.0192777078948229800494895064532553117703859768210647632969276, 0.0348034950916108873629241563077465542944938906271231198634442, -0.0340715609308469289267379681032545422644143611273049912226126, 1 ) ); return evecs4; } } // namespace agarose // Compute center of gravity template vec computeCenter(const std::vector& testData) { double c[4]; std::fill(c, c + vec::length(), 0.0); typename std::vector::const_iterator e = testData.end(); for(typename std::vector::const_iterator i = testData.begin(); i != e; ++i) for(glm::length_t d = 0; d < static_cast(vec::length()); ++d) c[d] += static_cast((*i)[d]); vec cVec(0); for(glm::length_t d = 0; d < static_cast(vec::length()); ++d) cVec[d] = static_cast(c[d] / static_cast(testData.size())); return cVec; } // Test sorting of Eigenvalue&Eigenvector lists. Use exhaustive search. template int testEigenvalueSort() { // Test input data: four arbitrary values static const glm::vec refVal( glm::vec<4, T, Q>( 10, 8, 6, 4 ) ); // Test input data: four arbitrary vectors, which can be matched to the above values static const glm::mat refVec( glm::mat<4, 4, T, Q>( 10, 20, 5, 40, 8, 16, 4, 32, 6, 12, 3, 24, 4, 8, 2, 16 ) ); // Permutations of test input data for exhaustive check, based on `D` (1 <= D <= 4) static const int permutationCount[] = { 0, 1, 2, 6, 24 }; // The permutations t perform, based on `D` (1 <= D <= 4) static const glm::ivec4 permutation[] = { glm::ivec4(0, 1, 2, 3), glm::ivec4(1, 0, 2, 3), // last for D = 2 glm::ivec4(0, 2, 1, 3), glm::ivec4(1, 2, 0, 3), glm::ivec4(2, 0, 1, 3), glm::ivec4(2, 1, 0, 3), // last for D = 3 glm::ivec4(0, 1, 3, 2), glm::ivec4(1, 0, 3, 2), glm::ivec4(0, 2, 3, 1), glm::ivec4(1, 2, 3, 0), glm::ivec4(2, 0, 3, 1), glm::ivec4(2, 1, 3, 0), glm::ivec4(0, 3, 1, 2), glm::ivec4(1, 3, 0, 2), glm::ivec4(0, 3, 2, 1), glm::ivec4(1, 3, 2, 0), glm::ivec4(2, 3, 0, 1), glm::ivec4(2, 3, 1, 0), glm::ivec4(3, 0, 1, 2), glm::ivec4(3, 1, 0, 2), glm::ivec4(3, 0, 2, 1), glm::ivec4(3, 1, 2, 0), glm::ivec4(3, 2, 0, 1), glm::ivec4(3, 2, 1, 0) // last for D = 4 }; // initial sanity check if(!vectorEpsilonEqual(refVal, refVal, myEpsilon())) return failReport(__LINE__); if(!matrixEpsilonEqual(refVec, refVec, myEpsilon())) return failReport(__LINE__); // Exhaustive search through all permutations for(int p = 0; p < permutationCount[D]; ++p) { glm::vec testVal; glm::mat testVec; for(int i = 0; i < D; ++i) { testVal[i] = refVal[permutation[p][i]]; testVec[i] = refVec[permutation[p][i]]; } glm::sortEigenvalues(testVal, testVec); if (!vectorEpsilonEqual(testVal, refVal, myEpsilon())) return failReport(__LINE__); if (!matrixEpsilonEqual(testVec, refVec, myEpsilon())) return failReport(__LINE__); } return 0; } // Test covariance matrix creation functions template int testCovar( #if GLM_HAS_CXX11_STL == 1 glm::length_t dataSize, unsigned int randomEngineSeed #else // GLM_HAS_CXX11_STL == 1 glm::length_t, unsigned int #endif // GLM_HAS_CXX11_STL == 1 ) { typedef glm::vec vec; typedef glm::mat mat; // #1: test expected result with fixed data set std::vector testData; agarose::fillTestData(testData); // compute center of gravity vec center = computeCenter(testData); mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center); if(!matrixEpsilonEqual(covarMat, mat(agarose::expectedCovarData()), myEpsilon())) { fprintf(stderr, "Reconstructed covarMat:\n%s\n", glm::to_string(covarMat).c_str()); return failReport(__LINE__); } // #2: test function variant consitency with random data #if GLM_HAS_CXX11_STL == 1 std::default_random_engine rndEng(randomEngineSeed); std::normal_distribution normalDist; testData.resize(dataSize); // some common offset of all data T offset[D]; for(glm::length_t d = 0; d < D; ++d) offset[d] = normalDist(rndEng); // init data for(glm::length_t i = 0; i < dataSize; ++i) for(glm::length_t d = 0; d < D; ++d) testData[i][d] = offset[d] + normalDist(rndEng); center = computeCenter(testData); std::vector centeredTestData; centeredTestData.reserve(testData.size()); typename std::vector::const_iterator e = testData.end(); for(typename std::vector::const_iterator i = testData.begin(); i != e; ++i) centeredTestData.push_back((*i) - center); mat c1 = glm::computeCovarianceMatrix(centeredTestData.data(), centeredTestData.size()); mat c2 = glm::computeCovarianceMatrix(centeredTestData.begin(), centeredTestData.end()); mat c3 = glm::computeCovarianceMatrix(testData.data(), testData.size(), center); mat c4 = glm::computeCovarianceMatrix(testData.rbegin(), testData.rend(), center); if(!matrixEpsilonEqual(c1, c2, myEpsilon())) return failReport(__LINE__); if(!matrixEpsilonEqual(c1, c3, myEpsilon())) return failReport(__LINE__); if(!matrixEpsilonEqual(c1, c4, myEpsilon())) return failReport(__LINE__); #endif // GLM_HAS_CXX11_STL == 1 return 0; } // Computes eigenvalues and eigenvectors from well-known covariance matrix template int testEigenvectors(T epsilon) { typedef glm::vec vec; typedef glm::mat mat; // test expected result with fixed data set std::vector testData; mat covarMat(agarose::expectedCovarData()); vec eigenvalues; mat eigenvectors; unsigned int c = glm::findEigenvaluesSymReal(covarMat, eigenvalues, eigenvectors); if(c != D) return failReport(__LINE__); glm::sortEigenvalues(eigenvalues, eigenvectors); if (!vectorEpsilonEqual(eigenvalues, vec(agarose::expectedEigenvalues()), epsilon)) return failReport(__LINE__); for (int i = 0; i < D; ++i) { vec act = glm::normalize(eigenvectors[i]); vec exp = glm::normalize(agarose::expectedEigenvectors()[i]); if (!sameSign(act[0], exp[0])) exp = -exp; if (!vectorEpsilonEqual(act, exp, epsilon)) return failReport(__LINE__); } return 0; } // A simple small smoke test: // - a uniformly sampled block // - reconstruct main axes // - check order of eigenvalues equals order of extends of block in direction of main axes int smokeTest() { using glm::vec3; using glm::mat3; std::vector pts; pts.reserve(11 * 15 * 7); for(int x = -5; x <= 5; ++x) for(int y = -7; y <= 7; ++y) for(int z = -3; z <= 3; ++z) pts.push_back(vec3(x, y, z)); mat3 covar = glm::computeCovarianceMatrix(pts.data(), pts.size()); mat3 eVec; vec3 eVal; int eCnt = glm::findEigenvaluesSymReal(covar, eVal, eVec); if(eCnt != 3) return failReport(__LINE__); // sort eVec by decending eVal if(eVal[0] < eVal[1]) { std::swap(eVal[0], eVal[1]); std::swap(eVec[0], eVec[1]); } if(eVal[0] < eVal[2]) { std::swap(eVal[0], eVal[2]); std::swap(eVec[0], eVec[2]); } if(eVal[1] < eVal[2]) { std::swap(eVal[1], eVal[2]); std::swap(eVec[1], eVec[2]); } if(!vectorEpsilonEqual(glm::abs(eVec[0]), vec3(0, 1, 0), myEpsilon())) return failReport(__LINE__); if(!vectorEpsilonEqual(glm::abs(eVec[1]), vec3(1, 0, 0), myEpsilon())) return failReport(__LINE__); if(!vectorEpsilonEqual(glm::abs(eVec[2]), vec3(0, 0, 1), myEpsilon())) return failReport(__LINE__); return 0; } #if GLM_HAS_CXX11_STL == 1 int rndTest(unsigned int randomEngineSeed) { std::default_random_engine rndEng(randomEngineSeed); std::normal_distribution normalDist; // construct orthonormal system glm::dvec3 x(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); double l = glm::length(x); while(l < myEpsilon()) x = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); x = glm::normalize(x); glm::dvec3 y(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); l = glm::length(y); while(l < myEpsilon()) y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); while(glm::abs(glm::dot(x, y)) < myEpsilon()) { y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); while(l < myEpsilon()) y = glm::dvec3(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); } y = glm::normalize(y); glm::dvec3 z = glm::normalize(glm::cross(x, y)); y = glm::normalize(glm::cross(z, x)); // generate input point data std::vector ptData; static const int pattern[] = { 8, 0, 0, 4, 1, 2, 0, 2, 0, 0, 0, 4 }; glm::dvec3 offset(normalDist(rndEng), normalDist(rndEng), normalDist(rndEng)); for(int p = 0; p < 4; ++p) for(int xs = 1; xs >= -1; xs -= 2) for(int ys = 1; ys >= -1; ys -= 2) for(int zs = 1; zs >= -1; zs -= 2) ptData.push_back( offset + x * static_cast(pattern[p * 3 + 0] * xs) + y * static_cast(pattern[p * 3 + 1] * ys) + z * static_cast(pattern[p * 3 + 2] * zs)); // perform PCA: glm::dvec3 center = computeCenter(ptData); glm::dmat3 covarMat = glm::computeCovarianceMatrix(ptData.data(), ptData.size(), center); glm::dvec3 evals; glm::dmat3 evecs; int evcnt = glm::findEigenvaluesSymReal(covarMat, evals, evecs); if(evcnt != 3) return failReport(__LINE__); glm::sortEigenvalues(evals, evecs); if (!sameSign(evecs[0][0], x[0])) evecs[0] = -evecs[0]; if(!vectorEpsilonEqual(x, evecs[0], myEpsilon())) return failReport(__LINE__); if (!sameSign(evecs[2][0], y[0])) evecs[2] = -evecs[2]; if (!vectorEpsilonEqual(y, evecs[2], myEpsilon())) return failReport(__LINE__); if (!sameSign(evecs[1][0], z[0])) evecs[1] = -evecs[1]; if (!vectorEpsilonEqual(z, evecs[1], myEpsilon())) return failReport(__LINE__); return 0; } #endif // GLM_HAS_CXX11_STL == 1 int main() { int error(0); // A small smoke test to fail early with most problems if(smokeTest()) return failReport(__LINE__); // test sorting utility. if(testEigenvalueSort<2, float, glm::defaultp>() != 0) error = failReport(__LINE__); if(testEigenvalueSort<2, double, glm::defaultp>() != 0) error = failReport(__LINE__); if(testEigenvalueSort<3, float, glm::defaultp>() != 0) error = failReport(__LINE__); if(testEigenvalueSort<3, double, glm::defaultp>() != 0) error = failReport(__LINE__); if(testEigenvalueSort<4, float, glm::defaultp>() != 0) error = failReport(__LINE__); if(testEigenvalueSort<4, double, glm::defaultp>() != 0) error = failReport(__LINE__); if (error != 0) return error; // Note: the random engine uses a fixed seed to create consistent and reproducible test data // test covariance matrix computation from different data sources if(testCovar<2, float, glm::defaultp>(100, 12345) != 0) error = failReport(__LINE__); if(testCovar<2, double, glm::defaultp>(100, 42) != 0) error = failReport(__LINE__); if(testCovar<3, float, glm::defaultp>(100, 2021) != 0) error = failReport(__LINE__); if(testCovar<3, double, glm::defaultp>(100, 815) != 0) error = failReport(__LINE__); if(testCovar<4, float, glm::defaultp>(100, 3141) != 0) error = failReport(__LINE__); if(testCovar<4, double, glm::defaultp>(100, 174) != 0) error = failReport(__LINE__); if (error != 0) return error; // test PCA eigen vector reconstruction // Expected epsilon precision evaluated separately: // https://github.com/sgrottel/exp-pca-precision if(testEigenvectors<2, float, glm::defaultp>(0.002f) != 0) error = failReport(__LINE__); if(testEigenvectors<2, double, glm::defaultp>(0.00000000001) != 0) error = failReport(__LINE__); if(testEigenvectors<3, float, glm::defaultp>(0.00001f) != 0) error = failReport(__LINE__); if(testEigenvectors<3, double, glm::defaultp>(0.0000000001) != 0) error = failReport(__LINE__); if(testEigenvectors<4, float, glm::defaultp>(0.0001f) != 0) error = failReport(__LINE__); if(testEigenvectors<4, double, glm::defaultp>(0.0000001) != 0) error = failReport(__LINE__); if(error != 0) return error; // Final tests with randomized data #if GLM_HAS_CXX11_STL == 1 if(rndTest(12345) != 0) error = failReport(__LINE__); if(rndTest(42) != 0) error = failReport(__LINE__); if (error != 0) return error; #endif // GLM_HAS_CXX11_STL == 1 return error; }