/* ---------------------------------------------------------------------------- * GTSAM Copyright 2010, Georgia Tech Research Corporation, * Atlanta, Georgia 30332-0415 * All Rights Reserved * Authors: Frank Dellaert, et al. (see THANKS for the full author list) * See LICENSE for the license information * -------------------------------------------------------------------------- */ /** * @file testGaussianConditional.cpp * @brief Unit tests for Conditional gaussian * @author Christian Potthast **/ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace gtsam; using namespace std; using symbol_shorthand::X; using symbol_shorthand::Y; static const double tol = 1e-5; static Matrix R = (Matrix(2, 2) << -12.1244, -5.1962, 0., 4.6904).finished(); using Dims = std::vector; /* ************************************************************************* */ TEST(GaussianConditional, constructor) { Matrix S1 = (Matrix(2, 2) << -5.2786, -8.6603, 5.0254, 5.5432).finished(); Matrix S2 = (Matrix(2, 2) << -10.5573, -5.9385, 5.5737, 3.0153).finished(); Matrix S3 = (Matrix(2, 2) << -11.3820, -7.2581, -3.0153, -3.5635).finished(); Vector d = Vector2(1.0, 2.0); SharedDiagonal s = noiseModel::Diagonal::Sigmas(Vector2(3.0, 4.0)); vector > terms = {{1, R}, {3, S1}, {5, S2}, {7, S3}}; GaussianConditional actual(terms, 1, d, s); GaussianConditional::const_iterator it = actual.beginFrontals(); EXPECT(assert_equal(Key(1), *it)); EXPECT(assert_equal(R, actual.R())); ++ it; EXPECT(it == actual.endFrontals()); it = actual.beginParents(); EXPECT(assert_equal(Key(3), *it)); EXPECT(assert_equal(S1, actual.S(it))); ++ it; EXPECT(assert_equal(Key(5), *it)); EXPECT(assert_equal(S2, actual.S(it))); ++ it; EXPECT(assert_equal(Key(7), *it)); EXPECT(assert_equal(S3, actual.S(it))); ++it; EXPECT(it == actual.endParents()); EXPECT(assert_equal(d, actual.d())); EXPECT(assert_equal(*s, *actual.get_model())); // test copy constructor GaussianConditional copied(actual); EXPECT(assert_equal(d, copied.d())); EXPECT(assert_equal(*s, *copied.get_model())); EXPECT(assert_equal(R, copied.R())); } /* ************************************************************************* */ TEST( GaussianConditional, equals ) { // create a conditional gaussian node Matrix A1(2,2); A1(0,0) = 1 ; A1(1,0) = 2; A1(0,1) = 3 ; A1(1,1) = 4; Matrix A2(2,2); A2(0,0) = 6 ; A2(1,0) = 0.2; A2(0,1) = 8 ; A2(1,1) = 0.4; Matrix R(2,2); R(0,0) = 0.1 ; R(1,0) = 0.3; R(0,1) = 0.0 ; R(1,1) = 0.34; SharedDiagonal model = noiseModel::Diagonal::Sigmas(Vector2(1.0, 0.34)); Vector d = Vector2(0.2, 0.5); GaussianConditional expected(1, d, R, 2, A1, 10, A2, model), actual(1, d, R, 2, A1, 10, A2, model); EXPECT( expected.equals(actual) ); } /* ************************************************************************* */ namespace density { static const Key key = 77; static constexpr double sigma = 3.0; static const auto unitPrior = GaussianConditional(key, Vector1::Constant(5), I_1x1), widerPrior = GaussianConditional( key, Vector1::Constant(5), I_1x1, noiseModel::Isotropic::Sigma(1, sigma)); } // namespace density /* ************************************************************************* */ // Check that the evaluate function matches direct calculation with R. TEST(GaussianConditional, Evaluate1) { // Let's evaluate at the mean const VectorValues mean = density::unitPrior.solve(VectorValues()); // We get the Hessian matrix, which has noise model applied! const Matrix invSigma = density::unitPrior.information(); // A Gaussian density ~ exp (-0.5*(Rx-d)'*(Rx-d)) // which at the mean is 1.0! So, the only thing we need to calculate is // the normalization constant 1.0/sqrt((2*pi*Sigma).det()). // The covariance matrix inv(Sigma) = R'*R, so the determinant is const double expected = sqrt((invSigma / (2 * M_PI)).determinant()); const double actual = density::unitPrior.evaluate(mean); EXPECT_DOUBLES_EQUAL(expected, actual, 1e-9); using density::key; using density::sigma; // Check Invariants at the mean and a different value for (auto vv : {mean, VectorValues{{key, Vector1(4)}}}) { EXPECT(GaussianConditional::CheckInvariants(density::unitPrior, vv)); EXPECT(GaussianConditional::CheckInvariants(density::unitPrior, HybridValues{vv, {}, {}})); } // Let's numerically integrate and see that we integrate to 1.0. double integral = 0.0; // Loop from -5*sigma to 5*sigma in 0.1*sigma steps: for (double x = -5.0 * sigma; x <= 5.0 * sigma; x += 0.1 * sigma) { VectorValues xValues; xValues.insert(key, mean.at(key) + Vector1(x)); const double density = density::unitPrior.evaluate(xValues); integral += 0.1 * sigma * density; } EXPECT_DOUBLES_EQUAL(1.0, integral, 1e-9); } /* ************************************************************************* */ // Check the evaluate with non-unit noise. TEST(GaussianConditional, Evaluate2) { // See comments in test above. const VectorValues mean = density::widerPrior.solve(VectorValues()); const Matrix R = density::widerPrior.R(); const Matrix invSigma = density::widerPrior.information(); const double expected = sqrt((invSigma / (2 * M_PI)).determinant()); const double actual = density::widerPrior.evaluate(mean); EXPECT_DOUBLES_EQUAL(expected, actual, 1e-9); using density::key; using density::sigma; // Check Invariants at the mean and a different value for (auto vv : {mean, VectorValues{{key, Vector1(4)}}}) { EXPECT(GaussianConditional::CheckInvariants(density::widerPrior, vv)); EXPECT(GaussianConditional::CheckInvariants(density::widerPrior, HybridValues{vv, {}, {}})); } // Let's numerically integrate and see that we integrate to 1.0. double integral = 0.0; // Loop from -5*sigma to 5*sigma in 0.1*sigma steps: for (double x = -5.0 * sigma; x <= 5.0 * sigma; x += 0.1 * sigma) { VectorValues xValues; xValues.insert(key, mean.at(key) + Vector1(x)); const double density = density::widerPrior.evaluate(xValues); integral += 0.1 * sigma * density; } EXPECT_DOUBLES_EQUAL(1.0, integral, 1e-5); } /* ************************************************************************* */ TEST( GaussianConditional, solve ) { //expected solution Vector expectedX(2); expectedX(0) = 20-3-11 ; expectedX(1) = 40-7-15; // create a conditional Gaussian node Matrix R = (Matrix(2, 2) << 1., 0., 0., 1.).finished(); Matrix A1 = (Matrix(2, 2) << 1., 2., 3., 4.).finished(); Matrix A2 = (Matrix(2, 2) << 5., 6., 7., 8.).finished(); Vector d(2); d << 20.0, 40.0; GaussianConditional cg(1, d, R, 2, A1, 10, A2); Vector sx1(2); sx1 << 1.0, 1.0; Vector sl1(2); sl1 << 1.0, 1.0; VectorValues expected = {{1, expectedX}, {2, sx1}, {10, sl1}}; VectorValues solution = {{2, sx1}, // parents {10, sl1}}; solution.insert(cg.solve(solution)); EXPECT(assert_equal(expected, solution, tol)); } /* ************************************************************************* */ TEST( GaussianConditional, solve_simple ) { // 2 variables, frontal has dim=4 VerticalBlockMatrix blockMatrix(Dims{4, 2, 1}, 4); blockMatrix.matrix() << 1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 0.1, 0.0, 1.0, 0.0, 2.0, 0.0, 3.0, 0.2, 0.0, 0.0, 3.0, 0.0, 4.0, 0.0, 0.3, 0.0, 0.0, 0.0, 3.0, 0.0, 4.0, 0.4; // solve system as a non-multifrontal version first GaussianConditional cg(KeyVector{1,2}, 1, blockMatrix); // partial solution Vector sx1 = Vector2(9.0, 10.0); // elimination order: 1, 2 VectorValues actual = {{2, sx1}}; // parent VectorValues expected = { {2, sx1}, {1, (Vector(4) << -3.1, -3.4, -11.9, -13.2).finished()}}; // verify indices/size EXPECT_LONGS_EQUAL(2, (long)cg.size()); EXPECT_LONGS_EQUAL(4, (long)cg.rows()); // solve and verify actual.insert(cg.solve(actual)); EXPECT(assert_equal(expected, actual, tol)); } /* ************************************************************************* */ TEST( GaussianConditional, solve_multifrontal ) { // create full system, 3 variables, 2 frontals, all 2 dim VerticalBlockMatrix blockMatrix(Dims{2, 2, 2, 1}, 4); blockMatrix.matrix() << 1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 0.1, 0.0, 1.0, 0.0, 2.0, 0.0, 3.0, 0.2, 0.0, 0.0, 3.0, 0.0, 4.0, 0.0, 0.3, 0.0, 0.0, 0.0, 3.0, 0.0, 4.0, 0.4; // 3 variables, all dim=2 GaussianConditional cg(KeyVector{1, 2, 10}, 2, blockMatrix); EXPECT(assert_equal(Vector(blockMatrix.full().rightCols(1)), cg.d())); // partial solution Vector sl1 = Vector2(9.0, 10.0); // elimination order; _x_, _x1_, _l1_ VectorValues actual = {{10, sl1}}; // parent VectorValues expected = { {1, Vector2(-3.1, -3.4)}, {2, Vector2(-11.9, -13.2)}, {10, sl1}}; // verify indices/size EXPECT_LONGS_EQUAL(3, (long)cg.size()); EXPECT_LONGS_EQUAL(4, (long)cg.rows()); // solve and verify actual.insert(cg.solve(actual)); EXPECT(assert_equal(expected, actual, tol)); } /* ************************************************************************* */ TEST( GaussianConditional, solveTranspose ) { /** create small Chordal Bayes Net x <- y * x y d * 1 1 9 * 1 5 */ Matrix R11 = (Matrix(1, 1) << 1.0).finished(), S12 = (Matrix(1, 1) << 1.0).finished(); Matrix R22 = (Matrix(1, 1) << 1.0).finished(); Vector d1(1), d2(1); d1(0) = 9; d2(0) = 5; // define nodes and specify in reverse topological sort (i.e. parents last) GaussianBayesNet cbn; cbn.emplace_shared(1, d1, R11, 2, S12); cbn.emplace_shared(1, d2, R22); // x=R'*y, y=inv(R')*x // 2 = 1 2 // 5 1 1 3 VectorValues x = {{1, (Vector(1) << 2.).finished()}, {2, (Vector(1) << 5.).finished()}}, y = {{1, (Vector(1) << 2.).finished()}, {2, (Vector(1) << 3.).finished()}}; // test functional version VectorValues actual = cbn.backSubstituteTranspose(x); CHECK(assert_equal(y, actual)); } /* ************************************************************************* */ TEST( GaussianConditional, information ) { // Create R matrix Matrix R(4,4); R << 1, 2, 3, 4, 0, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 10; // Create conditional GaussianConditional conditional(0, Vector::Zero(4), R); // Expected information matrix (using permuted R) Matrix IExpected = R.transpose() * R; // Actual information matrix (conditional should permute R) Matrix IActual = conditional.information(); EXPECT(assert_equal(IExpected, IActual)); } /* ************************************************************************* */ TEST( GaussianConditional, isGaussianFactor ) { // Create R matrix Matrix R(4,4); R << 1, 2, 3, 4, 0, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 10; // Create a conditional GaussianConditional conditional(0, Vector::Zero(4), R); // Expected information matrix computed by conditional Matrix IExpected = conditional.information(); // Expected information matrix computed by a factor JacobianFactor jf = conditional; Matrix IActual = jf.information(); EXPECT(assert_equal(IExpected, IActual)); } /* ************************************************************************* */ // Test FromMeanAndStddev named constructors TEST(GaussianConditional, FromMeanAndStddev) { Matrix A1 = (Matrix(2, 2) << 1., 2., 3., 4.).finished(); Matrix A2 = (Matrix(2, 2) << 5., 6., 7., 8.).finished(); const Vector2 b(20, 40), x0(1, 2), x1(3, 4), x2(5, 6); const double sigma = 3; VectorValues values{{X(0), x0}, {X(1), x1}, {X(2), x2}}; auto conditional1 = GaussianConditional::FromMeanAndStddev(X(0), A1, X(1), b, sigma); Vector2 e1 = (x0 - (A1 * x1 + b)) / sigma; double expected1 = 0.5 * e1.dot(e1); EXPECT_DOUBLES_EQUAL(expected1, conditional1.error(values), 1e-9); auto conditional2 = GaussianConditional::FromMeanAndStddev(X(0), A1, X(1), A2, X(2), b, sigma); Vector2 e2 = (x0 - (A1 * x1 + A2 * x2 + b)) / sigma; double expected2 = 0.5 * e2.dot(e2); EXPECT_DOUBLES_EQUAL(expected2, conditional2.error(values), 1e-9); // Check Invariants for both conditionals for (auto conditional : {conditional1, conditional2}) { EXPECT(GaussianConditional::CheckInvariants(conditional, values)); EXPECT(GaussianConditional::CheckInvariants(conditional, HybridValues{values, {}, {}})); } } /* ************************************************************************* */ // Test likelihood method (conversion to JacobianFactor) TEST(GaussianConditional, likelihood) { Matrix A1 = (Matrix(2, 2) << 1., 2., 3., 4.).finished(); const Vector2 b(20, 40), x0(1, 2); const double sigma = 0.01; // |x0 - A1 x1 - b|^2 auto conditional = GaussianConditional::FromMeanAndStddev(X(0), A1, X(1), b, sigma); VectorValues frontalValues; frontalValues.insert(X(0), x0); auto actual1 = conditional.likelihood(frontalValues); CHECK(actual1); // |(-A1) x1 - (b - x0)|^2 JacobianFactor expected(X(1), -A1, b - x0, noiseModel::Isotropic::Sigma(2, sigma)); EXPECT(assert_equal(expected, *actual1, tol)); // Check single vector version auto actual2 = conditional.likelihood(x0); CHECK(actual2); EXPECT(assert_equal(expected, *actual2, tol)); } /* ************************************************************************* */ // Test sampling TEST(GaussianConditional, sample) { Matrix A1 = (Matrix(2, 2) << 1., 2., 3., 4.).finished(); const Vector2 b(20, 40), x1(3, 4); const double sigma = 0.01; auto density = GaussianDensity::FromMeanAndStddev(X(0), b, sigma); auto actual1 = density.sample(); EXPECT_LONGS_EQUAL(1, actual1.size()); EXPECT(assert_equal(b, actual1[X(0)], 50 * sigma)); VectorValues given; given.insert(X(1), x1); auto conditional = GaussianConditional::FromMeanAndStddev(X(0), A1, X(1), b, sigma); auto actual2 = conditional.sample(given); EXPECT_LONGS_EQUAL(1, actual2.size()); EXPECT(assert_equal(A1 * x1 + b, actual2[X(0)], 50 * sigma)); // Use a specific random generator std::mt19937_64 rng(4242); auto actual3 = conditional.sample(given, &rng); EXPECT_LONGS_EQUAL(1, actual2.size()); // regression is not repeatable across platforms/versions :-( // EXPECT(assert_equal(Vector2(31.0111856, 64.9850775), actual2[X(0)], 1e-5)); } /* ************************************************************************* */ TEST(GaussianConditional, Error) { // Create univariate standard gaussian conditional auto stdGaussian = GaussianConditional::FromMeanAndStddev(X(0), Vector1::Zero(), 1.0); VectorValues values; values.insert(X(0), Vector1::Zero()); double logProbability = stdGaussian.logProbability(values); // Regression. // These values were computed by hand for a univariate standard gaussian. EXPECT_DOUBLES_EQUAL(-0.9189385332046727, logProbability, 1e-9); EXPECT_DOUBLES_EQUAL(0.3989422804014327, exp(logProbability), 1e-9); EXPECT_DOUBLES_EQUAL(stdGaussian(values), exp(logProbability), 1e-9); } /* ************************************************************************* */ // Similar test for multivariate gaussian but with sigma 2.0 TEST(GaussianConditional, LogNormalizationConstant) { double sigma = 2.0; auto conditional = GaussianConditional::FromMeanAndStddev(X(0), Vector3::Zero(), sigma); VectorValues x; x.insert(X(0), Vector3::Zero()); Matrix3 Sigma = I_3x3 * sigma * sigma; double expectedLogNormalizingConstant = log(1 / sqrt((2 * M_PI * Sigma).determinant())); EXPECT_DOUBLES_EQUAL(expectedLogNormalizingConstant, conditional.logNormalizationConstant(), 1e-9); } /* ************************************************************************* */ TEST(GaussianConditional, Print) { Matrix A1 = (Matrix(2, 2) << 1., 2., 3., 4.).finished(); Matrix A2 = (Matrix(2, 2) << 5., 6., 7., 8.).finished(); const Vector2 b(20, 40); const double sigma = 3; GaussianConditional conditional(X(0), b, Matrix2::Identity(), noiseModel::Isotropic::Sigma(2, sigma)); // Test printing for no parents. std::string expected = "GaussianConditional p(x0)\n" " R = [ 1 0 ]\n" " [ 0 1 ]\n" " d = [ 20 40 ]\n" " mean: 1 elements\n" " x0: 20 40\n" " logNormalizationConstant: -4.0351\n" "isotropic dim=2 sigma=3\n"; EXPECT(assert_print_equal(expected, conditional, "GaussianConditional")); auto conditional1 = GaussianConditional::FromMeanAndStddev(X(0), A1, X(1), b, sigma); // Test printing for single parent. std::string expected1 = "GaussianConditional p(x0 | x1)\n" " R = [ 1 0 ]\n" " [ 0 1 ]\n" " S[x1] = [ -1 -2 ]\n" " [ -3 -4 ]\n" " d = [ 20 40 ]\n" " logNormalizationConstant: -4.0351\n" "isotropic dim=2 sigma=3\n"; EXPECT(assert_print_equal(expected1, conditional1, "GaussianConditional")); // Test printing for multiple parents. auto conditional2 = GaussianConditional::FromMeanAndStddev(X(0), A1, Y(0), A2, Y(1), b, sigma); std::string expected2 = "GaussianConditional p(x0 | y0 y1)\n" " R = [ 1 0 ]\n" " [ 0 1 ]\n" " S[y0] = [ -1 -2 ]\n" " [ -3 -4 ]\n" " S[y1] = [ -5 -6 ]\n" " [ -7 -8 ]\n" " d = [ 20 40 ]\n" " logNormalizationConstant: -4.0351\n" "isotropic dim=2 sigma=3\n"; EXPECT(assert_print_equal(expected2, conditional2, "GaussianConditional")); } /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr); } /* ************************************************************************* */