Merge pull request #1077 from sgrottel/gtx-pca-test-rework

Reworked test for GTX PCA #1077 #1071
master
Christophe ago%!(EXTRA string=4 years) committed by GitHub
commit 8efb7408b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 358
      test/gtx/gtx_pca.cpp

@ -17,33 +17,47 @@ GLM_INLINE GLM_CONSTEXPR float myEpsilon<float>() { return 0.00001f; }
template<> template<>
GLM_INLINE GLM_CONSTEXPR double myEpsilon<double>() { return 0.000001; } GLM_INLINE GLM_CONSTEXPR double myEpsilon<double>() { return 0.000001; }
template<typename T>
T myEpsilon2();
template<>
GLM_INLINE GLM_CONSTEXPR float myEpsilon2<float>() { return 0.01f; }
template<>
GLM_INLINE GLM_CONSTEXPR double myEpsilon2<double>() { return 0.000001; }
template<glm::length_t D, typename T, glm::qualifier Q> template<glm::length_t D, typename T, glm::qualifier Q>
bool vectorEpsilonEqual(glm::vec<D, T, Q> const& a, glm::vec<D, T, Q> const& b) bool vectorEpsilonEqual(glm::vec<D, T, Q> const& a, glm::vec<D, T, Q> const& b, T epsilon)
{ {
for (int c = 0; c < D; ++c) for (int c = 0; c < D; ++c)
if (!glm::epsilonEqual(a[c], b[c], myEpsilon<T>())) if (!glm::epsilonEqual(a[c], b[c], epsilon))
{
fprintf(stderr, "failing vectorEpsilonEqual: [%d] %lf != %lf (~%lf)\n",
c,
static_cast<double>(a[c]),
static_cast<double>(b[c]),
static_cast<double>(epsilon)
);
return false; return false;
}
return true; return true;
} }
template<glm::length_t D, typename T, glm::qualifier Q> template<glm::length_t D, typename T, glm::qualifier Q>
bool matrixEpsilonEqual(glm::mat<D, D, T, Q> const& a, glm::mat<D, D, T, Q> const& b) bool matrixEpsilonEqual(glm::mat<D, D, T, Q> const& a, glm::mat<D, D, T, Q> const& b, T epsilon)
{ {
for (int c = 0; c < D; ++c) for (int c = 0; c < D; ++c)
for (int r = 0; r < D; ++r) for (int r = 0; r < D; ++r)
if (!glm::epsilonEqual(a[c][r], b[c][r], myEpsilon<T>())) if (!glm::epsilonEqual(a[c][r], b[c][r], epsilon))
{
fprintf(stderr, "failing vectorEpsilonEqual: [%d][%d] %lf != %lf (~%lf)\n",
c, r,
static_cast<double>(a[c][r]),
static_cast<double>(b[c][r]),
static_cast<double>(epsilon)
);
return false; return false;
}
return true; return true;
} }
template<typename T>
GLM_INLINE bool sameSign(T const& a, T const& b)
{
return ((a >= 0) && (b >= 0)) || ((a < 0) && (b < 0));
}
template<typename T> template<typename T>
T failReport(T line) T failReport(T line)
{ {
@ -200,153 +214,123 @@ namespace _1aga
outTestData[i][d] = static_cast<typename vec::value_type>(_1aga[i * 4 + d]); outTestData[i][d] = static_cast<typename vec::value_type>(_1aga[i * 4 + d]);
} }
void getExpectedCovarDataPtr(const double*& ptr) // All reference values computed separately using symbolic precision
{ // https://github.com/sgrottel/exp-pca-precision
static const double _1agaCovar4x4d[] = { // This applies to all functions named: `_1aga::expected*()`
9.624340680272107, -0.000066573696146, -4.293213765684049, 0.018793741874528,
-0.000066573696146, 9.624439378684805, 5.351138726379443, -0.115692591458806,
-4.293213765684049, 5.351138726379443, 35.628485496346691, 0.908742392542202,
0.018793741874528, -0.115692591458806, 0.908742392542202, 1.097059718568909
};
ptr = _1agaCovar4x4d;
}
void getExpectedCovarDataPtr(const float*& ptr)
{
// note: the value difference to `_1agaCovar4x4d` is due to the numeric error propagation during computation of the covariance matrix.
static const float _1agaCovar4x4f[] = {
9.624336242675781f, -0.000066711785621f, -4.293214797973633f, 0.018793795257807f,
-0.000066711785621f, 9.624438285827637f, 5.351140022277832f, -0.115692682564259f,
-4.293214797973633f, 5.351140022277832f, 35.628479003906250f, 0.908742427825928f,
0.018793795257807f, -0.115692682564259f, 0.908742427825928f, 1.097059369087219f
};
ptr = _1agaCovar4x4f;
}
template<glm::length_t D, typename T, glm::qualifier Q> GLM_INLINE glm::dmat4 const& expectedCovarData()
int checkCovarMat(glm::mat<D, D, T, Q> const& covarMat)
{
const T* expectedCovarData = GLM_NULLPTR;
getExpectedCovarDataPtr(expectedCovarData);
for(glm::length_t x = 0; x < D; ++x)
for(glm::length_t y = 0; y < D; ++y)
if(!glm::equal(covarMat[y][x], expectedCovarData[x * 4 + y], myEpsilon<T>()))
{ {
fprintf(stderr, "E: %.15lf != %.15lf ; diff: %.20lf\n", static const glm::dmat4 covar4x4d(
static_cast<double>(covarMat[y][x]), 9.62434068027210898322, -0.00006657369614512471, -4.29321376568405099761, 0.01879374187452758846,
static_cast<double>(expectedCovarData[x * 4 + y]), -0.00006657369614512471, 9.62443937868480681175, 5.35113872637944076871, -0.11569259145880574080,
static_cast<double>(covarMat[y][x] - expectedCovarData[x * 4 + y]) -4.29321376568405099761, 5.35113872637944076871, 35.62848549634668415820, 0.90874239254220201545,
0.01879374187452758846, -0.11569259145880574080, 0.90874239254220201545, 1.09705971856890904803
); );
return failReport(__LINE__); return covar4x4d;
}
return 0;
} }
template<glm::length_t D, typename T> void getExpectedEigenvaluesEigenvectorsDataPtr(const T*& evals, const T*& evecs); template<glm::length_t D>
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<2, float>(const float*& evals, const float*& evecs) GLM_INLINE glm::vec<D, double, glm::defaultp> const& expectedEigenvalues();
{ template<>
static const float expectedEvals[] = { GLM_INLINE glm::dvec2 const& expectedEigenvalues<2>()
9.624471664428711f, 9.624302864074707f
};
static const float expectedEvecs[] = {
-0.443000972270966f, 0.896521151065826f,
0.896521151065826f, 0.443000972270966f
};
evals = expectedEvals;
evecs = expectedEvecs;
}
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<2, double>(const double*& evals, const double*& evecs)
{ {
static const double expectedEvals[] = { static const glm::dvec2 evals2(
9.624472899262972, 9.624307159693940 9.62447289926297399961763301774251330057894539467032275382255,
}; 9.62430715969394210015560961264297422776572580714373620309355
static const double expectedEvecs[] = { );
-0.449720461624363, 0.893169360421846, return evals2;
0.893169360421846, 0.449720461624363
};
evals = expectedEvals;
evecs = expectedEvecs;
} }
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<3, float>(const float*& evals, const float*& evecs) template<>
GLM_INLINE glm::dvec3 const& expectedEigenvalues<3>()
{ {
static const float expectedEvals[] = { static const glm::dvec3 evals3(
37.327442169189453f, 9.624311447143555f, 7.925499439239502f 37.3274494274683425233695502581182052836449738530676689472257,
}; 9.62431434161498823505729817436585077939509766554969096873168,
static const float expectedEvecs[] = { 7.92550178622027216422369326567668971675332732240052872097887
-0.150428697466850f, 0.187497511506081f, 0.970678031444550f, );
0.779980957508087f, 0.625803351402283f, -0.000005212802080f, return evals3;
0.607454538345337f, -0.757109522819519f, 0.240383237600327f
};
evals = expectedEvals;
evecs = expectedEvecs;
} }
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<3, double>(const double*& evals, const double*& evecs) template<>
GLM_INLINE glm::dvec4 const& expectedEigenvalues<4>()
{ {
static const double expectedEvals[] = { static const glm::dvec4 evals4(
37.327449427468345, 9.624314341614987, 7.925501786220276 37.3477389918792213596879452204499702406947817221901007885630,
}; 9.62470688921105696017807313860277172063600080413412567999700,
static const double expectedEvecs[] = { 7.94017075281634999342344275928070533134615133171969063657713,
-0.150428640509585, 0.187497426513576, 0.970678082149394, 1.06170863996588365446060186982477896078741484440002343404155
0.779981605126846, 0.625802441381904, -0.000004919018357, );
0.607453635908278, -0.757110308615089, 0.240383154173870 return evals4;
};
evals = expectedEvals;
evecs = expectedEvecs;
} }
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<4, float>(const float*& evals, const float*& evecs)
template<glm::length_t D>
GLM_INLINE glm::mat<D, D, double, glm::defaultp> const& expectedEigenvectors();
template<>
GLM_INLINE glm::dmat2 const& expectedEigenvectors<2>()
{ {
static const float expectedEvals[] = { static const glm::dmat2 evecs2(
37.347740173339844f, 9.624703407287598f, 7.940164566040039f, 1.061712265014648f glm::dvec2(
}; -0.503510847492551904906870957742619139443409162857537237123308,
static const float expectedEvecs[] = { 1
-0.150269940495491f, 0.187220811843872f, 0.970467865467072f, 0.023652425035834f, ),
0.779159665107727f, 0.626788496971130f, -0.000105984276161f, -0.006797631736845f, glm::dvec2(
0.608242213726044f, -0.755563497543335f, 0.238818943500519f, 0.046158745884895f, 1.98605453086051402895741763848787613048533838388005162794043,
-0.019251370802522f, 0.034755907952785f, -0.034024771302938f, 0.998630762100220f, 1
}; )
evals = expectedEvals; );
evecs = expectedEvecs; return evecs2;
} }
template<> void getExpectedEigenvaluesEigenvectorsDataPtr<4, double>(const double*& evals, const double*& evecs) template<>
GLM_INLINE glm::dmat3 const& expectedEigenvectors<3>()
{ {
static const double expectedEvals[] = { static const glm::dmat3 evecs3(
37.347738991879226, 9.624706889211053, 7.940170752816341, 1.061708639965897 glm::dvec3(
}; -0.154972738414395866005286433008304444294405085038689821864654,
static const double expectedEvecs[] = { 0.193161285869815165989799191097521722568079378840201629578695,
-0.150269954805403, 0.187220917596058, 0.970467838469868, 0.023652551509145, 1
0.779159831346545, 0.626788431871120, -0.000105940250315, -0.006797622027466, ),
0.608241962267880, -0.755563776664248, 0.238818902950296, 0.046158707986616, glm::dvec3(
-0.019251317755512, 0.034755849578017, -0.034024915369495, 0.998630924225204, -158565.112775416943154745839952575022429933119522746586149868,
}; -127221.506282351944358932458687410410814983610301927832439675,
evals = expectedEvals; 1
evecs = expectedEvecs; ),
glm::dvec3(
2.52702248596556806145700361724323960543858113426446460406536,
-3.14959802931313870497377546974185300816008580801457419079412,
1
)
);
return evecs3;
} }
template<>
template<glm::length_t D, typename T, glm::qualifier Q> GLM_INLINE glm::dmat4 const& expectedEigenvectors<4>()
int checkEigenvaluesEigenvectors(
glm::vec<D, T, Q> const& evals,
glm::mat<D, D, T, Q> const& evecs)
{
const T* expectedEvals = GLM_NULLPTR;
const T* expectedEvecs = GLM_NULLPTR;
getExpectedEigenvaluesEigenvectorsDataPtr<D, T>(expectedEvals, expectedEvecs);
for(int i = 0; i < D; ++i)
if(!glm::equal(evals[i], expectedEvals[i], myEpsilon<T>()))
return failReport(__LINE__);
for (int i = 0; i < D; ++i)
for (int d = 0; d < D; ++d)
if (!glm::equal(evecs[i][d], expectedEvecs[i * D + d], myEpsilon2<T>()))
{ {
fprintf(stderr, "E: %.15lf != %.15lf ; diff: %.20lf\n", static const glm::dmat4 evecs4(
static_cast<double>(evecs[i][d]), glm::dvec4(
static_cast<double>(expectedEvecs[i * D + d]), -6.35322390281037045217295803597357821705371650876122113027264,
static_cast<double>(evecs[i][d] - expectedEvecs[i * D + d]) 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 failReport(__LINE__); return evecs4;
}
return 0;
} }
} // namespace _1aga } // namespace _1aga
@ -425,9 +409,9 @@ int testEigenvalueSort()
}; };
// initial sanity check // initial sanity check
if(!vectorEpsilonEqual(refVal, refVal)) if(!vectorEpsilonEqual(refVal, refVal, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
if(!matrixEpsilonEqual(refVec, refVec)) if(!matrixEpsilonEqual(refVec, refVec, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
// Exhaustive search through all permutations // Exhaustive search through all permutations
@ -443,9 +427,9 @@ int testEigenvalueSort()
glm::sortEigenvalues(testVal, testVec); glm::sortEigenvalues(testVal, testVec);
if (!vectorEpsilonEqual(testVal, refVal)) if (!vectorEpsilonEqual(testVal, refVal, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
if (!matrixEpsilonEqual(testVec, refVec)) if (!matrixEpsilonEqual(testVec, refVec, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
} }
@ -473,7 +457,7 @@ int testCovar(
vec center = computeCenter(testData); vec center = computeCenter(testData);
mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center); mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
if(_1aga::checkCovarMat(covarMat)) if(!matrixEpsilonEqual(covarMat, mat(_1aga::expectedCovarData()), myEpsilon<T>()))
{ {
fprintf(stderr, "Reconstructed covarMat:\n%s\n", glm::to_string(covarMat).c_str()); fprintf(stderr, "Reconstructed covarMat:\n%s\n", glm::to_string(covarMat).c_str());
return failReport(__LINE__); return failReport(__LINE__);
@ -505,27 +489,27 @@ int testCovar(
mat c3 = glm::computeCovarianceMatrix(testData.data(), testData.size(), center); mat c3 = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
mat c4 = glm::computeCovarianceMatrix<D, T, Q>(testData.rbegin(), testData.rend(), center); mat c4 = glm::computeCovarianceMatrix<D, T, Q>(testData.rbegin(), testData.rend(), center);
if(!matrixEpsilonEqual(c1, c2)) if(!matrixEpsilonEqual(c1, c2, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
if(!matrixEpsilonEqual(c1, c3)) if(!matrixEpsilonEqual(c1, c3, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
if(!matrixEpsilonEqual(c1, c4)) if(!matrixEpsilonEqual(c1, c4, myEpsilon<T>()))
return failReport(__LINE__); return failReport(__LINE__);
#endif // GLM_HAS_CXX11_STL == 1 #endif // GLM_HAS_CXX11_STL == 1
return 0; return 0;
} }
// Computes eigenvalues and eigenvectors from well-known covariance matrix
template<glm::length_t D, typename T, glm::qualifier Q> template<glm::length_t D, typename T, glm::qualifier Q>
int testEigenvectors() int testEigenvectors(T epsilon)
{ {
typedef glm::vec<D, T, Q> vec; typedef glm::vec<D, T, Q> vec;
typedef glm::mat<D, D, T, Q> mat; typedef glm::mat<D, D, T, Q> mat;
// test expected result with fixed data set // test expected result with fixed data set
std::vector<vec> testData; std::vector<vec> testData;
_1aga::fillTestData(testData); mat covarMat(_1aga::expectedCovarData());
vec center = computeCenter(testData);
mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
vec eigenvalues; vec eigenvalues;
mat eigenvectors; mat eigenvectors;
unsigned int c = glm::findEigenvaluesSymReal(covarMat, eigenvalues, eigenvectors); unsigned int c = glm::findEigenvaluesSymReal(covarMat, eigenvalues, eigenvectors);
@ -533,16 +517,25 @@ int testEigenvectors()
return failReport(__LINE__); return failReport(__LINE__);
glm::sortEigenvalues(eigenvalues, eigenvectors); glm::sortEigenvalues(eigenvalues, eigenvectors);
if(_1aga::checkEigenvaluesEigenvectors(eigenvalues, eigenvectors) != 0) if (!vectorEpsilonEqual(eigenvalues, vec(_1aga::expectedEigenvalues<D>()), epsilon))
return failReport(__LINE__); return failReport(__LINE__);
for (int i = 0; i < D; ++i)
{
vec act = glm::normalize(eigenvectors[i]);
vec exp = glm::normalize(_1aga::expectedEigenvectors<D>()[i]);
if (!sameSign(act[0], exp[0])) exp = -exp;
if (!vectorEpsilonEqual(act, exp, epsilon))
return failReport(__LINE__);
}
return 0; return 0;
} }
/// A simple small smoke test: // A simple small smoke test:
/// - a uniformly sampled block // - a uniformly sampled block
/// - reconstruct main axes // - reconstruct main axes
/// - check order of eigenvalues equals order of extends of block in direction of main axes // - check order of eigenvalues equals order of extends of block in direction of main axes
int smokeTest() int smokeTest()
{ {
using glm::vec3; using glm::vec3;
@ -579,11 +572,11 @@ int smokeTest()
std::swap(eVec[1], eVec[2]); std::swap(eVec[1], eVec[2]);
} }
if(!vectorEpsilonEqual(glm::abs(eVec[0]), vec3(0, 1, 0))) if(!vectorEpsilonEqual(glm::abs(eVec[0]), vec3(0, 1, 0), myEpsilon<float>()))
return failReport(__LINE__); return failReport(__LINE__);
if(!vectorEpsilonEqual(glm::abs(eVec[1]), vec3(1, 0, 0))) if(!vectorEpsilonEqual(glm::abs(eVec[1]), vec3(1, 0, 0), myEpsilon<float>()))
return failReport(__LINE__); return failReport(__LINE__);
if(!vectorEpsilonEqual(glm::abs(eVec[2]), vec3(0, 0, 1))) if(!vectorEpsilonEqual(glm::abs(eVec[2]), vec3(0, 0, 1), myEpsilon<float>()))
return failReport(__LINE__); return failReport(__LINE__);
return 0; return 0;
@ -615,14 +608,9 @@ int rndTest(unsigned int randomEngineSeed)
glm::dvec3 z = glm::normalize(glm::cross(x, y)); glm::dvec3 z = glm::normalize(glm::cross(x, y));
y = glm::normalize(glm::cross(z, x)); y = glm::normalize(glm::cross(z, x));
//printf("\n");
//printf("x: %.10lf, %.10lf, %.10lf\n", x.x, x.y, x.z);
//printf("y: %.10lf, %.10lf, %.10lf\n", y.x, y.y, y.z);
//printf("z: %.10lf, %.10lf, %.10lf\n", z.x, z.y, z.z);
// generate input point data // generate input point data
std::vector<glm::dvec3> ptData; std::vector<glm::dvec3> ptData;
static const int patters[] = { static const int pattern[] = {
8, 0, 0, 8, 0, 0,
4, 1, 2, 4, 1, 2,
0, 2, 0, 0, 2, 0,
@ -635,9 +623,9 @@ int rndTest(unsigned int randomEngineSeed)
for(int zs = 1; zs >= -1; zs -= 2) for(int zs = 1; zs >= -1; zs -= 2)
ptData.push_back( ptData.push_back(
offset offset
+ x * static_cast<double>(patters[p * 3 + 0] * xs) + x * static_cast<double>(pattern[p * 3 + 0] * xs)
+ y * static_cast<double>(patters[p * 3 + 1] * ys) + y * static_cast<double>(pattern[p * 3 + 1] * ys)
+ z * static_cast<double>(patters[p * 3 + 2] * zs)); + z * static_cast<double>(pattern[p * 3 + 2] * zs));
// perform PCA: // perform PCA:
glm::dvec3 center = computeCenter(ptData); glm::dvec3 center = computeCenter(ptData);
@ -649,16 +637,14 @@ int rndTest(unsigned int randomEngineSeed)
return failReport(__LINE__); return failReport(__LINE__);
glm::sortEigenvalues(evals, evecs); glm::sortEigenvalues(evals, evecs);
//printf("\n"); if (!sameSign(evecs[0][0], x[0])) evecs[0] = -evecs[0];
//printf("evec0: %.10lf, %.10lf, %.10lf\n", evecs[0].x, evecs[0].y, evecs[0].z); if(!vectorEpsilonEqual(x, evecs[0], myEpsilon<double>()))
//printf("evec2: %.10lf, %.10lf, %.10lf\n", evecs[2].x, evecs[2].y, evecs[2].z);
//printf("evec1: %.10lf, %.10lf, %.10lf\n", evecs[1].x, evecs[1].y, evecs[1].z);
if(glm::length(glm::abs(x) - glm::abs(evecs[0])) > myEpsilon<double>())
return failReport(__LINE__); return failReport(__LINE__);
if(glm::length(glm::abs(y) - glm::abs(evecs[2])) > myEpsilon<double>()) if (!sameSign(evecs[2][0], y[0])) evecs[2] = -evecs[2];
if (!vectorEpsilonEqual(y, evecs[2], myEpsilon<double>()))
return failReport(__LINE__); return failReport(__LINE__);
if(glm::length(glm::abs(z) - glm::abs(evecs[1])) > myEpsilon<double>()) if (!sameSign(evecs[1][0], z[0])) evecs[1] = -evecs[1];
if (!vectorEpsilonEqual(z, evecs[1], myEpsilon<double>()))
return failReport(__LINE__); return failReport(__LINE__);
return 0; return 0;
@ -707,17 +693,19 @@ int main()
return error; return error;
// test PCA eigen vector reconstruction // test PCA eigen vector reconstruction
if(testEigenvectors<2, float, glm::defaultp>() != 0) // Expected epsilon precision evaluated separately:
// https://github.com/sgrottel/exp-pca-precision
if(testEigenvectors<2, float, glm::defaultp>(0.002f) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(testEigenvectors<2, double, glm::defaultp>() != 0) if(testEigenvectors<2, double, glm::defaultp>(0.00000000001) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(testEigenvectors<3, float, glm::defaultp>() != 0) if(testEigenvectors<3, float, glm::defaultp>(0.00001f) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(testEigenvectors<3, double, glm::defaultp>() != 0) if(testEigenvectors<3, double, glm::defaultp>(0.0000000001) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(testEigenvectors<4, float, glm::defaultp>() != 0) if(testEigenvectors<4, float, glm::defaultp>(0.0001f) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(testEigenvectors<4, double, glm::defaultp>() != 0) if(testEigenvectors<4, double, glm::defaultp>(0.0000001) != 0)
error = failReport(__LINE__); error = failReport(__LINE__);
if(error != 0) if(error != 0)
return error; return error;

Loading…
Cancel
Save