Merge pull request #677 from borglab/feature/sparseJacobian3
additional `sparseJacobian` functions (new)release/4.3a0
						commit
						8f13405f05
					
				| 
						 | 
					@ -0,0 +1,119 @@
 | 
				
			||||||
 | 
					/* ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * 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 SparseEigen.h
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @brief Utilities for creating Eigen sparse matrices (gtsam::SparseEigen)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @date Aug 2019
 | 
				
			||||||
 | 
					 * @author Mandy Xie
 | 
				
			||||||
 | 
					 * @author Fan Jiang
 | 
				
			||||||
 | 
					 * @author Gerry Chen
 | 
				
			||||||
 | 
					 * @author Frank Dellaert
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gtsam/linear/GaussianFactorGraph.h>
 | 
				
			||||||
 | 
					#include <gtsam/linear/VectorValues.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <Eigen/Sparse>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace gtsam {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Eigen::SparseMatrix<double> SparseEigen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Constructs an Eigen-format SparseMatrix of a GaussianFactorGraph
 | 
				
			||||||
 | 
					SparseEigen sparseJacobianEigen(
 | 
				
			||||||
 | 
					    const GaussianFactorGraph &gfg, const Ordering &ordering) {
 | 
				
			||||||
 | 
					  // TODO(gerry): eliminate copy/pasta by making GaussianFactorGraph version
 | 
				
			||||||
 | 
					  // more general, or by creating an Eigen::Triplet compatible wrapper for
 | 
				
			||||||
 | 
					  // boost::tuple return type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // First find dimensions of each variable
 | 
				
			||||||
 | 
					  std::map<Key, size_t> dims;
 | 
				
			||||||
 | 
					  for (const boost::shared_ptr<GaussianFactor> &factor : gfg) {
 | 
				
			||||||
 | 
					    if (!static_cast<bool>(factor)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto it = factor->begin(); it != factor->end(); ++it) {
 | 
				
			||||||
 | 
					      dims[*it] = factor->getDim(it);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Compute first scalar column of each variable
 | 
				
			||||||
 | 
					  size_t currentColIndex = 0;
 | 
				
			||||||
 | 
					  std::map<Key, size_t> columnIndices;
 | 
				
			||||||
 | 
					  for (const auto key : ordering) {
 | 
				
			||||||
 | 
					    columnIndices[key] = currentColIndex;
 | 
				
			||||||
 | 
					    currentColIndex += dims[key];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Iterate over all factors, adding sparse scalar entries
 | 
				
			||||||
 | 
					  std::vector<Eigen::Triplet<double>> entries;
 | 
				
			||||||
 | 
					  entries.reserve(60 * gfg.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t row = 0;
 | 
				
			||||||
 | 
					  for (const boost::shared_ptr<GaussianFactor> &factor : gfg) {
 | 
				
			||||||
 | 
					    if (!static_cast<bool>(factor)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Convert to JacobianFactor if necessary
 | 
				
			||||||
 | 
					    JacobianFactor::shared_ptr jacobianFactor(
 | 
				
			||||||
 | 
					        boost::dynamic_pointer_cast<JacobianFactor>(factor));
 | 
				
			||||||
 | 
					    if (!jacobianFactor) {
 | 
				
			||||||
 | 
					      HessianFactor::shared_ptr hessian(
 | 
				
			||||||
 | 
					          boost::dynamic_pointer_cast<HessianFactor>(factor));
 | 
				
			||||||
 | 
					      if (hessian)
 | 
				
			||||||
 | 
					        jacobianFactor.reset(new JacobianFactor(*hessian));
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        throw std::invalid_argument(
 | 
				
			||||||
 | 
					            "GaussianFactorGraph contains a factor that is neither a "
 | 
				
			||||||
 | 
					            "JacobianFactor nor a HessianFactor.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Whiten the factor and add entries for it
 | 
				
			||||||
 | 
					    // iterate over all variables in the factor
 | 
				
			||||||
 | 
					    const JacobianFactor whitened(jacobianFactor->whiten());
 | 
				
			||||||
 | 
					    for (JacobianFactor::const_iterator key = whitened.begin();
 | 
				
			||||||
 | 
					         key < whitened.end(); ++key) {
 | 
				
			||||||
 | 
					      JacobianFactor::constABlock whitenedA = whitened.getA(key);
 | 
				
			||||||
 | 
					      // find first column index for this key
 | 
				
			||||||
 | 
					      size_t column_start = columnIndices[*key];
 | 
				
			||||||
 | 
					      for (size_t i = 0; i < (size_t)whitenedA.rows(); i++)
 | 
				
			||||||
 | 
					        for (size_t j = 0; j < (size_t)whitenedA.cols(); j++) {
 | 
				
			||||||
 | 
					          double s = whitenedA(i, j);
 | 
				
			||||||
 | 
					          if (std::abs(s) > 1e-12)
 | 
				
			||||||
 | 
					            entries.emplace_back(row + i, column_start + j, s);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    JacobianFactor::constBVector whitenedb(whitened.getb());
 | 
				
			||||||
 | 
					    size_t bcolumn = currentColIndex;
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < (size_t)whitenedb.size(); i++) {
 | 
				
			||||||
 | 
					      double s = whitenedb(i);
 | 
				
			||||||
 | 
					      if (std::abs(s) > 1e-12) entries.emplace_back(row + i, bcolumn, s);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Increment row index
 | 
				
			||||||
 | 
					    row += jacobianFactor->rows();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ...and make a sparse matrix with it.
 | 
				
			||||||
 | 
					  SparseEigen Ab(row, currentColIndex + 1);
 | 
				
			||||||
 | 
					  Ab.setFromTriplets(entries.begin(), entries.end());
 | 
				
			||||||
 | 
					  return Ab;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SparseEigen sparseJacobianEigen(const GaussianFactorGraph &gfg) {
 | 
				
			||||||
 | 
					  return sparseJacobianEigen(gfg, Ordering(gfg.keys()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace gtsam
 | 
				
			||||||
| 
						 | 
					@ -36,9 +36,18 @@ using namespace boost::assign;
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
using namespace gtsam;
 | 
					using namespace gtsam;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// static SharedDiagonal
 | 
					typedef boost::tuple<size_t, size_t, double> BoostTriplet;
 | 
				
			||||||
//  sigma0_1 = noiseModel::Isotropic::Sigma(2,0.1), sigma_02 = noiseModel::Isotropic::Sigma(2,0.2),
 | 
					bool triplet_equal(BoostTriplet a, BoostTriplet b) {
 | 
				
			||||||
//  constraintModel = noiseModel::Constrained::All(2);
 | 
					  if (a.get<0>() == b.get<0>() && a.get<1>() == b.get<1>() &&
 | 
				
			||||||
 | 
					      a.get<2>() == b.get<2>()) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cout << "not equal:" << endl;
 | 
				
			||||||
 | 
					  cout << "\texpected: "
 | 
				
			||||||
 | 
					      "(" << a.get<0>() << ", " << a.get<1>() << ") = " << a.get<2>() << endl;
 | 
				
			||||||
 | 
					  cout << "\tactual:   "
 | 
				
			||||||
 | 
					      "(" << b.get<0>() << ", " << b.get<1>() << ") = " << b.get<2>() << endl;
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* ************************************************************************* */
 | 
					/* ************************************************************************* */
 | 
				
			||||||
TEST(GaussianFactorGraph, initialization) {
 | 
					TEST(GaussianFactorGraph, initialization) {
 | 
				
			||||||
| 
						 | 
					@ -74,8 +83,8 @@ TEST(GaussianFactorGraph, sparseJacobian) {
 | 
				
			||||||
  //  9 10  0 11 12 13
 | 
					  //  9 10  0 11 12 13
 | 
				
			||||||
  //  0  0  0 14 15 16
 | 
					  //  0  0  0 14 15 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Expected - NOTE that we transpose this!
 | 
					  // Expected
 | 
				
			||||||
  Matrix expectedT = (Matrix(16, 3) <<
 | 
					  Matrix expected = (Matrix(16, 3) <<
 | 
				
			||||||
      1., 1., 2.,
 | 
					      1., 1., 2.,
 | 
				
			||||||
      1., 2., 4.,
 | 
					      1., 2., 4.,
 | 
				
			||||||
      1., 3., 6.,
 | 
					      1., 3., 6.,
 | 
				
			||||||
| 
						 | 
					@ -93,17 +102,32 @@ TEST(GaussianFactorGraph, sparseJacobian) {
 | 
				
			||||||
      3., 6.,26.,
 | 
					      3., 6.,26.,
 | 
				
			||||||
      4., 6.,32.).finished();
 | 
					      4., 6.,32.).finished();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Matrix expected = expectedT.transpose();
 | 
					  // expected: in matlab format - NOTE the transpose!)
 | 
				
			||||||
 | 
					  Matrix expectedMatlab = expected.transpose();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GaussianFactorGraph gfg;
 | 
					  GaussianFactorGraph gfg;
 | 
				
			||||||
  SharedDiagonal model = noiseModel::Isotropic::Sigma(2, 0.5);
 | 
					  SharedDiagonal model = noiseModel::Isotropic::Sigma(2, 0.5);
 | 
				
			||||||
  gfg.add(0, (Matrix(2, 3) << 1., 2., 3., 5., 6., 7.).finished(), Vector2(4., 8.), model);
 | 
					  const Key x123 = 0, x45 = 1;
 | 
				
			||||||
  gfg.add(0, (Matrix(2, 3) << 9., 10., 0., 0., 0., 0.).finished(), 1,
 | 
					  gfg.add(x123, (Matrix(2, 3) << 1, 2, 3, 5, 6, 7).finished(),
 | 
				
			||||||
          (Matrix(2, 2) << 11., 12., 14., 15.).finished(), Vector2(13., 16.), model);
 | 
					          Vector2(4, 8), model);
 | 
				
			||||||
 | 
					  gfg.add(x123, (Matrix(2, 3) << 9, 10, 0, 0, 0, 0).finished(),
 | 
				
			||||||
 | 
					          x45,  (Matrix(2, 2) << 11, 12, 14, 15.).finished(),
 | 
				
			||||||
 | 
					          Vector2(13, 16), model);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Matrix actual = gfg.sparseJacobian_();
 | 
					  Matrix actual = gfg.sparseJacobian_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  EXPECT(assert_equal(expected, actual));
 | 
					  EXPECT(assert_equal(expectedMatlab, actual));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // BoostTriplets
 | 
				
			||||||
 | 
					  auto boostActual = gfg.sparseJacobian();
 | 
				
			||||||
 | 
					  // check the triplets size...
 | 
				
			||||||
 | 
					  EXPECT_LONGS_EQUAL(16, boostActual.size());
 | 
				
			||||||
 | 
					  // check content
 | 
				
			||||||
 | 
					  for (int i = 0; i < 16; i++) {
 | 
				
			||||||
 | 
					    EXPECT(triplet_equal(
 | 
				
			||||||
 | 
					        BoostTriplet(expected(i, 0) - 1, expected(i, 1) - 1, expected(i, 2)),
 | 
				
			||||||
 | 
					        boostActual.at(i)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* ************************************************************************* */
 | 
					/* ************************************************************************* */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					/* ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * 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    testSparseMatrix.cpp
 | 
				
			||||||
 | 
					 * @author  Mandy Xie
 | 
				
			||||||
 | 
					 * @author  Fan Jiang
 | 
				
			||||||
 | 
					 * @author  Gerry Chen
 | 
				
			||||||
 | 
					 * @author  Frank Dellaert
 | 
				
			||||||
 | 
					 * @date    Jan, 2021
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gtsam/linear/GaussianFactorGraph.h>
 | 
				
			||||||
 | 
					#include <gtsam/linear/SparseEigen.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/assign/list_of.hpp>
 | 
				
			||||||
 | 
					using boost::assign::list_of;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gtsam/base/TestableAssertions.h>
 | 
				
			||||||
 | 
					#include <CppUnitLite/TestHarness.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace std;
 | 
				
			||||||
 | 
					using namespace gtsam;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ************************************************************************* */
 | 
				
			||||||
 | 
					TEST(SparseEigen, sparseJacobianEigen) {
 | 
				
			||||||
 | 
					  GaussianFactorGraph gfg;
 | 
				
			||||||
 | 
					  SharedDiagonal model = noiseModel::Isotropic::Sigma(2, 0.5);
 | 
				
			||||||
 | 
					  const Key x123 = 0, x45 = 1;
 | 
				
			||||||
 | 
					  gfg.add(x123, (Matrix(2, 3) << 1, 2, 3, 5, 6, 7).finished(),
 | 
				
			||||||
 | 
					          Vector2(4, 8), model);
 | 
				
			||||||
 | 
					  gfg.add(x123, (Matrix(2, 3) << 9, 10, 0, 0, 0, 0).finished(),
 | 
				
			||||||
 | 
					          x45,  (Matrix(2, 2) << 11, 12, 14, 15.).finished(),
 | 
				
			||||||
 | 
					          Vector2(13, 16), model);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Sparse Matrix
 | 
				
			||||||
 | 
					  auto sparseResult = sparseJacobianEigen(gfg);
 | 
				
			||||||
 | 
					  EXPECT_LONGS_EQUAL(16, sparseResult.nonZeros());
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(4, sparseResult.rows()));
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(6, sparseResult.cols()));
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(gfg.augmentedJacobian(), Matrix(sparseResult)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Call sparseJacobian with optional ordering...
 | 
				
			||||||
 | 
					  auto ordering = Ordering(list_of(x45)(x123));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Eigen Sparse with optional ordering
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(gfg.augmentedJacobian(ordering),
 | 
				
			||||||
 | 
					                      Matrix(sparseJacobianEigen(gfg, ordering))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Check matrix dimensions when zero rows / cols
 | 
				
			||||||
 | 
					  gfg.add(x123, Matrix23::Zero(), Vector2::Zero(), model);  // zero row
 | 
				
			||||||
 | 
					  gfg.add(2, Matrix21::Zero(), Vector2::Zero(), model);     // zero col
 | 
				
			||||||
 | 
					  sparseResult = sparseJacobianEigen(gfg);
 | 
				
			||||||
 | 
					  EXPECT_LONGS_EQUAL(16, sparseResult.nonZeros());
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(8, sparseResult.rows()));
 | 
				
			||||||
 | 
					  EXPECT(assert_equal(7, sparseResult.cols()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ************************************************************************* */
 | 
				
			||||||
 | 
					int main() {
 | 
				
			||||||
 | 
					  TestResult tr;
 | 
				
			||||||
 | 
					  return TestRegistry::runAllTests(tr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/* ************************************************************************* */
 | 
				
			||||||
		Loading…
	
		Reference in New Issue