536 lines
19 KiB
C++
536 lines
19 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* 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 GaussianFactorGraph.cpp
|
|
* @brief Linear Factor Graph where all factors are Gaussians
|
|
* @author Kai Ni
|
|
* @author Christian Potthast
|
|
* @author Richard Roberts
|
|
* @author Frank Dellaert
|
|
*/
|
|
|
|
#include <gtsam/linear/GaussianFactorGraph.h>
|
|
#include <gtsam/linear/GaussianBayesTree.h>
|
|
#include <gtsam/linear/GaussianEliminationTree.h>
|
|
#include <gtsam/linear/GaussianJunctionTree.h>
|
|
#include <gtsam/linear/HessianFactor.h>
|
|
#include <gtsam/inference/FactorGraph-inst.h>
|
|
#include <gtsam/inference/EliminateableFactorGraph-inst.h>
|
|
#include <gtsam/base/debug.h>
|
|
#include <gtsam/base/timing.h>
|
|
#include <gtsam/base/cholesky.h>
|
|
|
|
using namespace std;
|
|
using namespace gtsam;
|
|
|
|
namespace gtsam {
|
|
|
|
// Instantiate base classes
|
|
template class FactorGraph<GaussianFactor>;
|
|
template class EliminateableFactorGraph<GaussianFactorGraph>;
|
|
|
|
/* ************************************************************************* */
|
|
bool GaussianFactorGraph::equals(const This& fg, double tol) const
|
|
{
|
|
return Base::equals(fg, tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GaussianFactorGraph::Keys GaussianFactorGraph::keys() const {
|
|
KeySet keys;
|
|
for (const sharedFactor& factor: *this)
|
|
if (factor)
|
|
keys.insert(factor->begin(), factor->end());
|
|
return keys;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
std::map<Key, size_t> GaussianFactorGraph::getKeyDimMap() const {
|
|
map<Key, size_t> spec;
|
|
for (const GaussianFactor::shared_ptr& gf : *this) {
|
|
for (GaussianFactor::const_iterator it = gf->begin(); it != gf->end(); it++) {
|
|
map<Key,size_t>::iterator it2 = spec.find(*it);
|
|
if ( it2 == spec.end() ) {
|
|
spec.emplace(*it, gf->getDim(it));
|
|
}
|
|
}
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GaussianFactorGraph::shared_ptr GaussianFactorGraph::cloneToPtr() const {
|
|
gtsam::GaussianFactorGraph::shared_ptr result(new GaussianFactorGraph());
|
|
*result = *this;
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GaussianFactorGraph GaussianFactorGraph::clone() const {
|
|
GaussianFactorGraph result;
|
|
for (const sharedFactor& f : *this) {
|
|
if (f)
|
|
result.push_back(f->clone());
|
|
else
|
|
result.push_back(sharedFactor()); // Passes on null factors so indices remain valid
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GaussianFactorGraph GaussianFactorGraph::negate() const {
|
|
GaussianFactorGraph result;
|
|
for (const sharedFactor& factor: *this) {
|
|
if (factor)
|
|
result.push_back(factor->negate());
|
|
else
|
|
result.push_back(sharedFactor()); // Passes on null factors so indices remain valid
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
using SparseTriplets = std::vector<std::tuple<int, int, double> >;
|
|
SparseTriplets GaussianFactorGraph::sparseJacobian(const Ordering& ordering,
|
|
size_t& nrows,
|
|
size_t& ncols) const {
|
|
gttic_(GaussianFactorGraph_sparseJacobian);
|
|
// First find dimensions of each variable
|
|
typedef std::map<Key, size_t> KeySizeMap;
|
|
KeySizeMap dims;
|
|
for (const auto& factor : *this) {
|
|
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
|
|
ncols = 0;
|
|
KeySizeMap columnIndices = dims;
|
|
for (const auto key : ordering) {
|
|
columnIndices[key] = ncols;
|
|
ncols += dims[key];
|
|
}
|
|
|
|
// Iterate over all factors, adding sparse scalar entries
|
|
SparseTriplets entries;
|
|
entries.reserve(30 * size());
|
|
nrows = 0;
|
|
for (const auto& factor : *this) {
|
|
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 (auto 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(nrows + i, column_start + j, s);
|
|
}
|
|
}
|
|
|
|
JacobianFactor::constBVector whitenedb(whitened.getb());
|
|
for (size_t i = 0; i < (size_t)whitenedb.size(); i++) {
|
|
double s = whitenedb(i);
|
|
if (std::abs(s) > 1e-12) entries.emplace_back(nrows + i, ncols, s);
|
|
}
|
|
|
|
// Increment row index
|
|
nrows += jacobianFactor->rows();
|
|
}
|
|
|
|
ncols++; // +1 for b-column
|
|
return entries;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
SparseTriplets GaussianFactorGraph::sparseJacobian() const {
|
|
size_t nrows, ncols;
|
|
return sparseJacobian(Ordering(this->keys()), nrows, ncols);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Matrix GaussianFactorGraph::sparseJacobian_() const {
|
|
gttic_(GaussianFactorGraph_sparseJacobian_matrix);
|
|
// call sparseJacobian
|
|
auto result = sparseJacobian();
|
|
|
|
// translate to base 1 matrix
|
|
size_t nzmax = result.size();
|
|
Matrix IJS(3, nzmax);
|
|
for (size_t k = 0; k < result.size(); k++) {
|
|
const auto& entry = result[k];
|
|
IJS(0, k) = double(std::get<0>(entry) + 1);
|
|
IJS(1, k) = double(std::get<1>(entry) + 1);
|
|
IJS(2, k) = std::get<2>(entry);
|
|
}
|
|
return IJS;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Matrix GaussianFactorGraph::augmentedJacobian(
|
|
const Ordering& ordering) const {
|
|
// combine all factors
|
|
JacobianFactor combined(*this, ordering);
|
|
return combined.augmentedJacobian();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Matrix GaussianFactorGraph::augmentedJacobian() const {
|
|
// combine all factors
|
|
JacobianFactor combined(*this);
|
|
return combined.augmentedJacobian();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
pair<Matrix, Vector> GaussianFactorGraph::jacobian(
|
|
const Ordering& ordering) const {
|
|
Matrix augmented = augmentedJacobian(ordering);
|
|
return make_pair(augmented.leftCols(augmented.cols() - 1),
|
|
augmented.col(augmented.cols() - 1));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
pair<Matrix, Vector> GaussianFactorGraph::jacobian() const {
|
|
Matrix augmented = augmentedJacobian();
|
|
return make_pair(augmented.leftCols(augmented.cols() - 1),
|
|
augmented.col(augmented.cols() - 1));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Matrix GaussianFactorGraph::augmentedHessian(
|
|
const Ordering& ordering) const {
|
|
// combine all factors and get upper-triangular part of Hessian
|
|
Scatter scatter(*this, ordering);
|
|
HessianFactor combined(*this, scatter);
|
|
return combined.info().selfadjointView();;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Matrix GaussianFactorGraph::augmentedHessian() const {
|
|
// combine all factors and get upper-triangular part of Hessian
|
|
Scatter scatter(*this);
|
|
HessianFactor combined(*this, scatter);
|
|
return combined.info().selfadjointView();;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
pair<Matrix, Vector> GaussianFactorGraph::hessian(
|
|
const Ordering& ordering) const {
|
|
Matrix augmented = augmentedHessian(ordering);
|
|
size_t n = augmented.rows() - 1;
|
|
return make_pair(augmented.topLeftCorner(n, n), augmented.topRightCorner(n, 1));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
pair<Matrix, Vector> GaussianFactorGraph::hessian() const {
|
|
Matrix augmented = augmentedHessian();
|
|
size_t n = augmented.rows() - 1;
|
|
return make_pair(augmented.topLeftCorner(n, n), augmented.topRightCorner(n, 1));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::hessianDiagonal() const {
|
|
VectorValues d;
|
|
for (const sharedFactor& factor : *this) {
|
|
if(factor){
|
|
factor->hessianDiagonalAdd(d);
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
map<Key,Matrix> GaussianFactorGraph::hessianBlockDiagonal() const {
|
|
map<Key,Matrix> blocks;
|
|
for (const sharedFactor& factor : *this) {
|
|
if (!factor) continue;
|
|
map<Key,Matrix> BD = factor->hessianBlockDiagonal();
|
|
map<Key,Matrix>::const_iterator it = BD.begin();
|
|
for (;it!=BD.end();++it) {
|
|
Key j = it->first; // variable key for this block
|
|
const Matrix& Bj = it->second;
|
|
if (blocks.count(j))
|
|
blocks[j] += Bj;
|
|
else
|
|
blocks.emplace(j,Bj);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
/* ************************************************************************ */
|
|
VectorValues GaussianFactorGraph::optimize(const Eliminate& function) const {
|
|
gttic(GaussianFactorGraph_optimize);
|
|
return BaseEliminateable::eliminateMultifrontal(Ordering::COLAMD, function)
|
|
->optimize();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::optimize(const Ordering& ordering, const Eliminate& function) const {
|
|
gttic(GaussianFactorGraph_optimize);
|
|
return BaseEliminateable::eliminateMultifrontal(ordering, function)->optimize();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// TODO(frank): can we cache memory across invocations
|
|
VectorValues GaussianFactorGraph::optimizeDensely() const {
|
|
gttic(GaussianFactorGraph_optimizeDensely);
|
|
|
|
// Combine all factors in a single HessianFactor (as done in augmentedHessian)
|
|
Scatter scatter(*this);
|
|
HessianFactor combined(*this, scatter);
|
|
|
|
// TODO(frank): cast to large dynamic matrix :-(
|
|
// NOTE(frank): info only valid (I think) in upper triangle. No problem for LLT...
|
|
Matrix augmented = combined.info().selfadjointView();
|
|
|
|
// Do Cholesky Factorization
|
|
size_t n = augmented.rows() - 1;
|
|
auto AtA = augmented.topLeftCorner(n, n);
|
|
auto eta = augmented.topRightCorner(n, 1);
|
|
Eigen::LLT<Matrix, Eigen::Upper> llt(AtA);
|
|
|
|
// Solve and convert, re-using scatter data structure
|
|
Vector solution = llt.solve(eta);
|
|
return VectorValues(solution, scatter);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
namespace {
|
|
JacobianFactor::shared_ptr convertToJacobianFactorPtr(const GaussianFactor::shared_ptr &gf) {
|
|
JacobianFactor::shared_ptr result = boost::dynamic_pointer_cast<JacobianFactor>(gf);
|
|
if( !result )
|
|
// Convert any non-Jacobian factors to Jacobians (e.g. Hessian -> Jacobian with Cholesky)
|
|
result = boost::make_shared<JacobianFactor>(*gf);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::gradient(const VectorValues& x0) const
|
|
{
|
|
VectorValues g = VectorValues::Zero(x0);
|
|
for (const sharedFactor& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
Vector e = Ai->error_vector(x0);
|
|
Ai->transposeMultiplyAdd(1.0, e, g);
|
|
}
|
|
return g;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::gradientAtZero() const {
|
|
// Zero-out the gradient
|
|
VectorValues g;
|
|
for (const sharedFactor& factor: *this) {
|
|
if (!factor) continue;
|
|
VectorValues gi = factor->gradientAtZero();
|
|
g.addInPlace_(gi);
|
|
}
|
|
return g;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::optimizeGradientSearch() const
|
|
{
|
|
gttic(GaussianFactorGraph_optimizeGradientSearch);
|
|
|
|
gttic(Compute_Gradient);
|
|
// Compute gradient (call gradientAtZero function, which is defined for various linear systems)
|
|
VectorValues grad = gradientAtZero();
|
|
double gradientSqNorm = grad.dot(grad);
|
|
gttoc(Compute_Gradient);
|
|
|
|
gttic(Compute_Rg);
|
|
// Compute R * g
|
|
Errors Rg = *this * grad;
|
|
gttoc(Compute_Rg);
|
|
|
|
gttic(Compute_minimizing_step_size);
|
|
// Compute minimizing step size
|
|
double step = -gradientSqNorm / dot(Rg, Rg);
|
|
gttoc(Compute_minimizing_step_size);
|
|
|
|
gttic(Compute_point);
|
|
// Compute steepest descent point
|
|
grad *= step;
|
|
gttoc(Compute_point);
|
|
|
|
return grad;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Errors GaussianFactorGraph::operator*(const VectorValues& x) const {
|
|
Errors e;
|
|
for (const GaussianFactor::shared_ptr& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
e.push_back((*Ai) * x);
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void GaussianFactorGraph::multiplyHessianAdd(double alpha,
|
|
const VectorValues& x, VectorValues& y) const {
|
|
for (const GaussianFactor::shared_ptr& f: *this)
|
|
f->multiplyHessianAdd(alpha, x, y);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void GaussianFactorGraph::multiplyInPlace(const VectorValues& x, Errors& e) const {
|
|
multiplyInPlace(x, e.begin());
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void GaussianFactorGraph::multiplyInPlace(const VectorValues& x, const Errors::iterator& e) const {
|
|
Errors::iterator ei = e;
|
|
for (const GaussianFactor::shared_ptr& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
*ei = (*Ai)*x;
|
|
ei++;
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool hasConstraints(const GaussianFactorGraph& factors) {
|
|
typedef JacobianFactor J;
|
|
for (const GaussianFactor::shared_ptr& factor: factors) {
|
|
J::shared_ptr jacobian(boost::dynamic_pointer_cast<J>(factor));
|
|
if (jacobian && jacobian->get_model() && jacobian->get_model()->isConstrained()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// x += alpha*A'*e
|
|
void GaussianFactorGraph::transposeMultiplyAdd(double alpha, const Errors& e,
|
|
VectorValues& x) const {
|
|
// For each factor add the gradient contribution
|
|
Errors::const_iterator ei = e.begin();
|
|
for (const sharedFactor& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
Ai->transposeMultiplyAdd(alpha, *(ei++), x);
|
|
}
|
|
}
|
|
|
|
///* ************************************************************************* */
|
|
//void residual(const GaussianFactorGraph& fg, const VectorValues &x, VectorValues &r) {
|
|
// Key i = 0 ;
|
|
// for (const GaussianFactor::shared_ptr& factor, fg) {
|
|
// JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
// r[i] = Ai->getb();
|
|
// i++;
|
|
// }
|
|
// VectorValues Ax = VectorValues::SameStructure(r);
|
|
// multiply(fg,x,Ax);
|
|
// axpy(-1.0,Ax,r);
|
|
//}
|
|
|
|
///* ************************************************************************* */
|
|
//void multiply(const GaussianFactorGraph& fg, const VectorValues &x, VectorValues &r) {
|
|
// r.setZero();
|
|
// Key i = 0;
|
|
// for (const GaussianFactor::shared_ptr& factor, fg) {
|
|
// JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
// Vector &y = r[i];
|
|
// for (JacobianFactor::const_iterator j = Ai->begin(); j != Ai->end(); ++j) {
|
|
// y += Ai->getA(j) * x[*j];
|
|
// }
|
|
// ++i;
|
|
// }
|
|
//}
|
|
|
|
/* ************************************************************************* */
|
|
VectorValues GaussianFactorGraph::transposeMultiply(const Errors& e) const
|
|
{
|
|
VectorValues x;
|
|
Errors::const_iterator ei = e.begin();
|
|
for (const sharedFactor& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
for (JacobianFactor::const_iterator j = Ai->begin(); j != Ai->end(); ++j) {
|
|
// Create the value as a zero vector if it does not exist.
|
|
pair<VectorValues::iterator, bool> xi = x.tryInsert(*j, Vector());
|
|
if(xi.second)
|
|
xi.first->second = Vector::Zero(Ai->getDim(j));
|
|
xi.first->second += Ai->getA(j).transpose() * *ei;
|
|
}
|
|
++ ei;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Errors GaussianFactorGraph::gaussianErrors(const VectorValues& x) const
|
|
{
|
|
Errors e;
|
|
for (const sharedFactor& factor: *this) {
|
|
JacobianFactor::shared_ptr Ai = convertToJacobianFactorPtr(factor);
|
|
e.push_back(Ai->error_vector(x));
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void GaussianFactorGraph::printErrors(
|
|
const VectorValues& values, const std::string& str,
|
|
const KeyFormatter& keyFormatter,
|
|
const std::function<bool(const Factor* /*factor*/,
|
|
double /*whitenedError*/, size_t /*index*/)>&
|
|
printCondition) const {
|
|
cout << str << "size: " << size() << endl << endl;
|
|
for (size_t i = 0; i < (*this).size(); i++) {
|
|
const sharedFactor& factor = (*this)[i];
|
|
const double errorValue =
|
|
(factor != nullptr ? (*this)[i]->error(values) : .0);
|
|
if (!printCondition(factor.get(), errorValue, i))
|
|
continue; // User-provided filter did not pass
|
|
|
|
stringstream ss;
|
|
ss << "Factor " << i << ": ";
|
|
if (factor == nullptr) {
|
|
cout << "nullptr"
|
|
<< "\n";
|
|
} else {
|
|
factor->print(ss.str(), keyFormatter);
|
|
cout << "error = " << errorValue << "\n";
|
|
}
|
|
cout << endl; // only one "endl" at end might be faster, \n for each
|
|
// factor
|
|
}
|
|
}
|
|
|
|
} // namespace gtsam
|