Explorar o código

Changed test functions to explicitly define comparison epsilons.

Updated 1aga expected computation results, based on external experiment based on precise / symbolic computation
Unified result comparison to always use `vectorEpsilonEqual` or `matrixEpsilonEqual`
SGrottel %!s(int64=4) %!d(string=hai) anos
pai
achega
5dbba35275
Modificáronse 1 ficheiros con 169 adicións e 187 borrados
  1. 169 187
      test/gtx/gtx_pca.cpp

+ 169 - 187
test/gtx/gtx_pca.cpp

@@ -17,30 +17,38 @@ GLM_INLINE GLM_CONSTEXPR float myEpsilon<float>() { return 0.00001f; }
 template<>
 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>
-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)
-		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 true;
 }
 
 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 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 true;
 }
 
@@ -200,153 +208,123 @@ namespace _1aga
 				outTestData[i][d] = static_cast<typename vec::value_type>(_1aga[i * 4 + d]);
 	}
 
-	void getExpectedCovarDataPtr(const double*& ptr)
-	{
-		static const double _1agaCovar4x4d[] = {
-			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;
-	}
+	// All reference values computed separately using symbolic precision
+	// https://github.com/sgrottel/exp-pca-precision
+	// This applies to all functions named: `_1aga::expected*()`
 
-	template<glm::length_t D, typename T, glm::qualifier Q>
-	int checkCovarMat(glm::mat<D, D, T, Q> const& covarMat)
+	GLM_INLINE glm::dmat4 const& expectedCovarData()
 	{
-		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_cast<double>(covarMat[y][x]),
-						static_cast<double>(expectedCovarData[x * 4 + y]),
-						static_cast<double>(covarMat[y][x] - expectedCovarData[x * 4 + y])
-					);
-					return failReport(__LINE__);
-				}
-		return 0;
+		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::length_t D, typename T> void getExpectedEigenvaluesEigenvectorsDataPtr(const T*& evals, const T*& evecs);
-	template<> void getExpectedEigenvaluesEigenvectorsDataPtr<2, float>(const float*& evals, const float*& evecs)
+	template<glm::length_t D>
+	GLM_INLINE glm::vec<D, double, glm::defaultp> const& expectedEigenvalues();
+	template<>
+	GLM_INLINE glm::dvec2 const& expectedEigenvalues<2>()
 	{
-		static const float expectedEvals[] = {
-			9.624471664428711f, 9.624302864074707f
-		};
-		static const float expectedEvecs[] = {
-			-0.443000972270966f, 0.896521151065826f,
-			0.896521151065826f, 0.443000972270966f
-		};
-		evals = expectedEvals;
-		evecs = expectedEvecs;
+		static const glm::dvec2 evals2(
+			9.62447289926297399961763301774251330057894539467032275382255,
+			9.62430715969394210015560961264297422776572580714373620309355
+		);
+		return evals2;
 	}
-	template<> void getExpectedEigenvaluesEigenvectorsDataPtr<2, double>(const double*& evals, const double*& evecs)
+	template<>
+	GLM_INLINE glm::dvec3 const& expectedEigenvalues<3>()
 	{
-		static const double expectedEvals[] = {
-			9.624472899262972, 9.624307159693940
-		};
-		static const double expectedEvecs[] = {
-			-0.449720461624363, 0.893169360421846,
-			0.893169360421846, 0.449720461624363
-		};
-		evals = expectedEvals;
-		evecs = expectedEvecs;
+		static const glm::dvec3 evals3(
+				37.3274494274683425233695502581182052836449738530676689472257,
+				9.62431434161498823505729817436585077939509766554969096873168,
+				7.92550178622027216422369326567668971675332732240052872097887
+			);
+		return evals3;
 	}
-	template<> void getExpectedEigenvaluesEigenvectorsDataPtr<3, float>(const float*& evals, const float*& evecs)
+	template<>
+	GLM_INLINE glm::dvec4 const& expectedEigenvalues<4>()
 	{
-		static const float expectedEvals[] = {
-			37.327442169189453f, 9.624311447143555f, 7.925499439239502f
-		};
-		static const float expectedEvecs[] = {
-			-0.150428697466850f, 0.187497511506081f, 0.970678031444550f,
-			0.779980957508087f, 0.625803351402283f, -0.000005212802080f,
-			0.607454538345337f, -0.757109522819519f, 0.240383237600327f
-		};
-		evals = expectedEvals;
-		evecs = expectedEvecs;
+		static const glm::dvec4 evals4(
+				37.3477389918792213596879452204499702406947817221901007885630,
+				9.62470688921105696017807313860277172063600080413412567999700,
+				7.94017075281634999342344275928070533134615133171969063657713,
+				1.06170863996588365446060186982477896078741484440002343404155
+			);
+		return evals4;
 	}
-	template<> void getExpectedEigenvaluesEigenvectorsDataPtr<3, double>(const double*& evals, const double*& evecs)
-	{
-		static const double expectedEvals[] = {
-			37.327449427468345, 9.624314341614987, 7.925501786220276
-		};
-		static const double expectedEvecs[] = {
-			-0.150428640509585, 0.187497426513576, 0.970678082149394,
-			0.779981605126846, 0.625802441381904, -0.000004919018357,
-			0.607453635908278, -0.757110308615089, 0.240383154173870
-		};
-		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[] = {
-			37.347740173339844f, 9.624703407287598f, 7.940164566040039f, 1.061712265014648f
-		};
-		static const float expectedEvecs[] = {
-			-0.150269940495491f, 0.187220811843872f, 0.970467865467072f, 0.023652425035834f,
-			0.779159665107727f, 0.626788496971130f, -0.000105984276161f, -0.006797631736845f,
-			0.608242213726044f, -0.755563497543335f, 0.238818943500519f, 0.046158745884895f,
-			-0.019251370802522f, 0.034755907952785f, -0.034024771302938f, 0.998630762100220f,
-		};
-		evals = expectedEvals;
-		evecs = expectedEvecs;
+		static const glm::dmat2 evecs2(
+			glm::dvec2(
+				-0.503510847492551904906870957742619139443409162857537237123308,
+				1
+			),
+			glm::dvec2(
+				1.98605453086051402895741763848787613048533838388005162794043,
+				1
+			)
+		);
+		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[] = {
-			37.347738991879226, 9.624706889211053, 7.940170752816341, 1.061708639965897
-		};
-		static const double expectedEvecs[] = {
-			-0.150269954805403, 0.187220917596058, 0.970467838469868, 0.023652551509145,
-			0.779159831346545, 0.626788431871120, -0.000105940250315, -0.006797622027466,
-			0.608241962267880, -0.755563776664248, 0.238818902950296, 0.046158707986616,
-			-0.019251317755512, 0.034755849578017, -0.034024915369495, 0.998630924225204,
-		};
-		evals = expectedEvals;
-		evecs = expectedEvecs;
+		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::length_t D, typename T, glm::qualifier Q>
-	int checkEigenvaluesEigenvectors(
-		glm::vec<D, T, Q> const& evals,
-		glm::mat<D, D, T, Q> const& evecs)
+	template<>
+	GLM_INLINE glm::dmat4 const& expectedEigenvectors<4>()
 	{
-		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_cast<double>(evecs[i][d]),
-						static_cast<double>(expectedEvecs[i * D + d]),
-						static_cast<double>(evecs[i][d] - expectedEvecs[i * D + d])
-					);
-					return failReport(__LINE__);
-				}
-
-		return 0;
+		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 _1aga
@@ -425,9 +403,9 @@ int testEigenvalueSort()
 	};
 
 	// initial sanity check
-	if(!vectorEpsilonEqual(refVal, refVal))
+	if(!vectorEpsilonEqual(refVal, refVal, myEpsilon<T>()))
 		return failReport(__LINE__);
-	if(!matrixEpsilonEqual(refVec, refVec))
+	if(!matrixEpsilonEqual(refVec, refVec, myEpsilon<T>()))
 		return failReport(__LINE__);
 
 	// Exhaustive search through all permutations
@@ -443,9 +421,9 @@ int testEigenvalueSort()
 
 		glm::sortEigenvalues(testVal, testVec);
 
-		if (!vectorEpsilonEqual(testVal, refVal))
+		if (!vectorEpsilonEqual(testVal, refVal, myEpsilon<T>()))
 			return failReport(__LINE__);
-		if (!matrixEpsilonEqual(testVec, refVec))
+		if (!matrixEpsilonEqual(testVec, refVec, myEpsilon<T>()))
 			return failReport(__LINE__);
 	}
 
@@ -473,7 +451,7 @@ int testCovar(
 	vec center = computeCenter(testData);
 
 	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());
 		return failReport(__LINE__);
@@ -505,27 +483,27 @@ int testCovar(
 	mat c3 = glm::computeCovarianceMatrix(testData.data(), testData.size(), 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__);
-	if(!matrixEpsilonEqual(c1, c3))
+	if(!matrixEpsilonEqual(c1, c3, myEpsilon<T>()))
 		return failReport(__LINE__);
-	if(!matrixEpsilonEqual(c1, c4))
+	if(!matrixEpsilonEqual(c1, c4, myEpsilon<T>()))
 		return failReport(__LINE__);
 #endif // GLM_HAS_CXX11_STL == 1
 	return 0;
 }
 
+// Computes eigenvalues and eigenvectors from well-known covariance matrix
 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::mat<D, D, T, Q> mat;
 
 	// test expected result with fixed data set
 	std::vector<vec> testData;
-	_1aga::fillTestData(testData);
-	vec center = computeCenter(testData);
-	mat covarMat = glm::computeCovarianceMatrix(testData.data(), testData.size(), center);
+	mat covarMat(_1aga::expectedCovarData());
+
 	vec eigenvalues;
 	mat eigenvectors;
 	unsigned int c = glm::findEigenvaluesSymReal(covarMat, eigenvalues, eigenvectors);
@@ -533,16 +511,25 @@ int testEigenvectors()
 		return failReport(__LINE__);
 	glm::sortEigenvalues(eigenvalues, eigenvectors);
 
-	if(_1aga::checkEigenvaluesEigenvectors(eigenvalues, eigenvectors) != 0)
+	if (!vectorEpsilonEqual(eigenvalues, vec(_1aga::expectedEigenvalues<D>()), epsilon))
 		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 (glm::sign(act[0]) != glm::sign(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
+// 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;
@@ -579,11 +566,11 @@ int smokeTest()
 		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__);
-	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__);
-	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 0;
@@ -615,14 +602,9 @@ int rndTest(unsigned int randomEngineSeed)
 	glm::dvec3 z = glm::normalize(glm::cross(x, y));
 	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
 	std::vector<glm::dvec3> ptData;
-	static const int patters[] = {
+	static const int pattern[] = {
 		8, 0, 0,
 		4, 1, 2,
 		0, 2, 0,
@@ -635,9 +617,9 @@ int rndTest(unsigned int randomEngineSeed)
 				for(int zs = 1; zs >= -1; zs -= 2)
 					ptData.push_back(
 						offset
-						+ x * static_cast<double>(patters[p * 3 + 0] * xs)
-						+ y * static_cast<double>(patters[p * 3 + 1] * ys)
-						+ z * static_cast<double>(patters[p * 3 + 2] * zs));
+						+ x * static_cast<double>(pattern[p * 3 + 0] * xs)
+						+ y * static_cast<double>(pattern[p * 3 + 1] * ys)
+						+ z * static_cast<double>(pattern[p * 3 + 2] * zs));
 
 	// perform PCA:
 	glm::dvec3 center = computeCenter(ptData);
@@ -649,16 +631,14 @@ int rndTest(unsigned int randomEngineSeed)
 		return failReport(__LINE__);
 	glm::sortEigenvalues(evals, evecs);
 
-	//printf("\n");
-	//printf("evec0: %.10lf, %.10lf, %.10lf\n", evecs[0].x, evecs[0].y, evecs[0].z);
-	//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>())
+	if (glm::sign(evecs[0][0]) != glm::sign(x[0])) evecs[0] = -evecs[0];
+	if(!vectorEpsilonEqual(x, evecs[0], myEpsilon<double>()))
 		return failReport(__LINE__);
-	if(glm::length(glm::abs(y) - glm::abs(evecs[2])) > myEpsilon<double>())
+	if (glm::sign(evecs[2][0]) != glm::sign(y[0])) evecs[2] = -evecs[2];
+	if (!vectorEpsilonEqual(y, evecs[2], myEpsilon<double>()))
 		return failReport(__LINE__);
-	if(glm::length(glm::abs(z) - glm::abs(evecs[1])) > myEpsilon<double>())
+	if (glm::sign(evecs[1][0]) != glm::sign(z[0])) evecs[1] = -evecs[1];
+	if (!vectorEpsilonEqual(z, evecs[1], myEpsilon<double>()))
 		return failReport(__LINE__);
 
 	return 0;
@@ -707,17 +687,19 @@ int main()
 		return error;
 
 	// 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__);
-	if(testEigenvectors<2, double, glm::defaultp>() != 0)
+	if(testEigenvectors<2, double, glm::defaultp>(0.00000000001) != 0)
 		error = failReport(__LINE__);
-	if(testEigenvectors<3, float, glm::defaultp>() != 0)
+	if(testEigenvectors<3, float, glm::defaultp>(0.00001f) != 0)
 		error = failReport(__LINE__);
-	if(testEigenvectors<3, double, glm::defaultp>() != 0)
+	if(testEigenvectors<3, double, glm::defaultp>(0.0000000001) != 0)
 		error = failReport(__LINE__);
-	if(testEigenvectors<4, float, glm::defaultp>() != 0)
+	if(testEigenvectors<4, float, glm::defaultp>(0.0001f) != 0)
 		error = failReport(__LINE__);
-	if(testEigenvectors<4, double, glm::defaultp>() != 0)
+	if(testEigenvectors<4, double, glm::defaultp>(0.0000001) != 0)
 		error = failReport(__LINE__);
 	if(error != 0)
 		return error;