346 lines
12 KiB
C++
346 lines
12 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 ISAM2Clique.cpp
|
|
* @brief Specialized iSAM2 Clique
|
|
* @author Michael Kaess, Richard Roberts, Frank Dellaert
|
|
*/
|
|
|
|
#include <gtsam/inference/BayesTreeCliqueBase-inst.h>
|
|
#include <gtsam/linear/VectorValues.h>
|
|
#include <gtsam/linear/linearAlgorithms-inst.h>
|
|
#include <gtsam/nonlinear/ISAM2Clique.h>
|
|
|
|
#include <stack>
|
|
#include <utility>
|
|
|
|
using namespace std;
|
|
|
|
namespace gtsam {
|
|
|
|
// Instantiate base class
|
|
template class BayesTreeCliqueBase<ISAM2Clique, GaussianFactorGraph>;
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::setEliminationResult(
|
|
const FactorGraphType::EliminationResult& eliminationResult) {
|
|
conditional_ = eliminationResult.first;
|
|
cachedFactor_ = eliminationResult.second;
|
|
// Compute gradient contribution
|
|
gradientContribution_.resize(conditional_->cols() - 1);
|
|
// Rewrite -(R * P')'*d as -(d' * R * P')' for computational speed
|
|
// reasons
|
|
gradientContribution_ << -conditional_->R().transpose() *
|
|
conditional_->d(),
|
|
-conditional_->S().transpose() * conditional_->d();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool ISAM2Clique::equals(const This& other, double tol) const {
|
|
return Base::equals(other) &&
|
|
((!cachedFactor_ && !other.cachedFactor_) ||
|
|
(cachedFactor_ && other.cachedFactor_ &&
|
|
cachedFactor_->equals(*other.cachedFactor_, tol)));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::print(const string& s, const KeyFormatter& formatter) const {
|
|
Base::print(s, formatter);
|
|
if (cachedFactor_)
|
|
cachedFactor_->print(s + "Cached: ", formatter);
|
|
else
|
|
cout << s << "Cached empty" << endl;
|
|
if (gradientContribution_.rows() != 0)
|
|
gtsam::print(gradientContribution_, "Gradient contribution: ");
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool ISAM2Clique::isDirty(const KeySet& replaced, const KeySet& changed) const {
|
|
// if none of the variables in this clique (frontal and separator!) changed
|
|
// significantly, then by the running intersection property, none of the
|
|
// cliques in the children need to be processed
|
|
|
|
// Are any clique variables part of the tree that has been redone?
|
|
bool dirty = replaced.exists(conditional_->frontals().front());
|
|
#if !defined(NDEBUG) && defined(GTSAM_EXTRA_CONSISTENCY_CHECKS)
|
|
for (Key frontal : conditional_->frontals()) {
|
|
assert(dirty == replaced.exists(frontal));
|
|
}
|
|
#endif
|
|
|
|
// If not, then has one of the separator variables changed significantly?
|
|
if (!dirty) {
|
|
for (Key parent : conditional_->parents()) {
|
|
if (changed.exists(parent)) {
|
|
dirty = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return dirty;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
/**
|
|
* Back-substitute - special version stores solution pointers in cliques for
|
|
* fast access.
|
|
*/
|
|
void ISAM2Clique::fastBackSubstitute(VectorValues* delta) const {
|
|
#ifdef USE_BROKEN_FAST_BACKSUBSTITUTE
|
|
// TODO(gareth): This code shares a lot of logic w/ linearAlgorithms-inst,
|
|
// potentially refactor
|
|
|
|
// Create solution part pointers if necessary and possible - necessary if
|
|
// solnPointers_ is empty, and possible if either we're a root, or we have
|
|
// a parent with valid solnPointers_.
|
|
ISAM2Clique::shared_ptr parent = parent_.lock();
|
|
if (solnPointers_.empty() && (isRoot() || !parent->solnPointers_.empty())) {
|
|
for (Key frontal : conditional_->frontals())
|
|
solnPointers_.emplace(frontal, delta->find(frontal));
|
|
for (Key parentKey : conditional_->parents()) {
|
|
assert(parent->solnPointers_.exists(parentKey));
|
|
solnPointers_.emplace(parentKey, parent->solnPointers_.at(parentKey));
|
|
}
|
|
}
|
|
|
|
// See if we can use solution part pointers - we can if they either
|
|
// already existed or were created above.
|
|
if (!solnPointers_.empty()) {
|
|
GaussianConditional& c = *conditional_;
|
|
// Solve matrix
|
|
Vector xS;
|
|
{
|
|
// Count dimensions of vector
|
|
DenseIndex dim = 0;
|
|
FastVector<VectorValues::const_iterator> parentPointers;
|
|
parentPointers.reserve(conditional_->nrParents());
|
|
for (Key parent : conditional_->parents()) {
|
|
parentPointers.push_back(solnPointers_.at(parent));
|
|
dim += parentPointers.back()->second.size();
|
|
}
|
|
|
|
// Fill parent vector
|
|
xS.resize(dim);
|
|
DenseIndex vectorPos = 0;
|
|
for (const VectorValues::const_iterator& parentPointer : parentPointers) {
|
|
const Vector& parentVector = parentPointer->second;
|
|
xS.block(vectorPos, 0, parentVector.size(), 1) =
|
|
parentVector.block(0, 0, parentVector.size(), 1);
|
|
vectorPos += parentVector.size();
|
|
}
|
|
}
|
|
|
|
// NOTE(gareth): We can no longer write: xS = b - S * xS
|
|
// This is because Eigen (as of 3.3) no longer evaluates S * xS into
|
|
// a temporary, and the operation trashes valus in xS.
|
|
// See: http://eigen.tuxfamily.org/index.php?title=3.3
|
|
const Vector rhs = c.getb() - c.S() * xS;
|
|
const Vector solution = c.R().triangularView<Eigen::Upper>().solve(rhs);
|
|
|
|
// Check for indeterminant solution
|
|
if (solution.hasNaN())
|
|
throw IndeterminantLinearSystemException(c.keys().front());
|
|
|
|
// Insert solution into a VectorValues
|
|
DenseIndex vectorPosition = 0;
|
|
for (GaussianConditional::const_iterator frontal = c.beginFrontals();
|
|
frontal != c.endFrontals(); ++frontal) {
|
|
solnPointers_.at(*frontal)->second =
|
|
solution.segment(vectorPosition, c.getDim(frontal));
|
|
vectorPosition += c.getDim(frontal);
|
|
}
|
|
} else {
|
|
// Just call plain solve because we couldn't use solution pointers.
|
|
delta->update(conditional_->solve(*delta));
|
|
}
|
|
#else
|
|
delta->update(conditional_->solve(*delta));
|
|
#endif
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool ISAM2Clique::valuesChanged(const KeySet& replaced,
|
|
const Vector& originalValues,
|
|
const VectorValues& delta,
|
|
double threshold) const {
|
|
auto frontals = conditional_->frontals();
|
|
if (replaced.exists(frontals.front())) return true;
|
|
Vector diff = originalValues - delta.vector(frontals);
|
|
return diff.lpNorm<Eigen::Infinity>() >= threshold;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
/// Set changed flag for each frontal variable
|
|
void ISAM2Clique::markFrontalsAsChanged(KeySet* changed) const {
|
|
for (Key frontal : conditional_->frontals()) {
|
|
changed->insert(frontal);
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::restoreFromOriginals(const Vector& originalValues,
|
|
VectorValues* delta) const {
|
|
size_t pos = 0;
|
|
for (Key frontal : conditional_->frontals()) {
|
|
auto v = delta->at(frontal);
|
|
v = originalValues.segment(pos, v.size());
|
|
pos += v.size();
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Note: not being used right now in favor of non-recursive version below.
|
|
void ISAM2Clique::optimizeWildfire(const KeySet& replaced, double threshold,
|
|
KeySet* changed, VectorValues* delta,
|
|
size_t* count) const {
|
|
if (isDirty(replaced, *changed)) {
|
|
// Temporary copy of the original values, to check how much they change
|
|
auto originalValues = delta->vector(conditional_->frontals());
|
|
|
|
// Back-substitute
|
|
fastBackSubstitute(delta);
|
|
count += conditional_->nrFrontals();
|
|
|
|
if (valuesChanged(replaced, originalValues, *delta, threshold)) {
|
|
markFrontalsAsChanged(changed);
|
|
} else {
|
|
restoreFromOriginals(originalValues, delta);
|
|
}
|
|
|
|
// Recurse to children
|
|
for (const auto& child : children) {
|
|
child->optimizeWildfire(replaced, threshold, changed, delta, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t optimizeWildfire(const ISAM2Clique::shared_ptr& root, double threshold,
|
|
const KeySet& keys, VectorValues* delta) {
|
|
KeySet changed;
|
|
size_t count = 0;
|
|
// starting from the root, call optimize on each conditional
|
|
if (root) root->optimizeWildfire(keys, threshold, &changed, delta, &count);
|
|
return count;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool ISAM2Clique::optimizeWildfireNode(const KeySet& replaced, double threshold,
|
|
KeySet* changed, VectorValues* delta,
|
|
size_t* count) const {
|
|
// TODO(gareth): This code shares a lot of logic w/ linearAlgorithms-inst,
|
|
// potentially refactor
|
|
bool dirty = isDirty(replaced, *changed);
|
|
if (dirty) {
|
|
// Temporary copy of the original values, to check how much they change
|
|
auto originalValues = delta->vector(conditional_->frontals());
|
|
|
|
// Back-substitute
|
|
fastBackSubstitute(delta);
|
|
*count += conditional_->nrFrontals();
|
|
|
|
if (valuesChanged(replaced, originalValues, *delta, threshold)) {
|
|
markFrontalsAsChanged(changed);
|
|
} else {
|
|
restoreFromOriginals(originalValues, delta);
|
|
}
|
|
}
|
|
|
|
return dirty;
|
|
}
|
|
|
|
size_t optimizeWildfireNonRecursive(const ISAM2Clique::shared_ptr& root,
|
|
double threshold, const KeySet& keys,
|
|
VectorValues* delta) {
|
|
KeySet changed;
|
|
size_t count = 0;
|
|
|
|
if (root) {
|
|
std::stack<ISAM2Clique::shared_ptr> travStack;
|
|
travStack.push(root);
|
|
ISAM2Clique::shared_ptr currentNode = root;
|
|
while (!travStack.empty()) {
|
|
currentNode = travStack.top();
|
|
travStack.pop();
|
|
bool dirty = currentNode->optimizeWildfireNode(keys, threshold, &changed,
|
|
delta, &count);
|
|
if (dirty) {
|
|
for (const auto& child : currentNode->children) {
|
|
travStack.push(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::nnz_internal(size_t* result) const {
|
|
size_t dimR = conditional_->rows();
|
|
size_t dimSep = conditional_->S().cols();
|
|
*result += ((dimR + 1) * dimR) / 2 + dimSep * dimR;
|
|
// traverse the children
|
|
for (const auto& child : children) {
|
|
child->nnz_internal(result);
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
size_t ISAM2Clique::calculate_nnz() const {
|
|
size_t result = 0;
|
|
nnz_internal(&result);
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::findAll(const KeySet& markedMask, KeySet* keys) const {
|
|
static const bool debug = false;
|
|
// does the separator contain any of the variables?
|
|
bool found = false;
|
|
for (Key key : conditional_->parents()) {
|
|
if (markedMask.exists(key)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
// then add this clique
|
|
keys->insert(conditional_->beginFrontals(), conditional_->endFrontals());
|
|
if (debug) print("Key(s) marked in clique ");
|
|
if (debug) cout << "so marking key " << conditional_->front() << endl;
|
|
}
|
|
for (const auto& child : children) {
|
|
child->findAll(markedMask, keys);
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void ISAM2Clique::addGradientAtZero(VectorValues* g) const {
|
|
// Loop through variables in each clique, adding contributions
|
|
DenseIndex position = 0;
|
|
for (auto it = conditional_->begin(); it != conditional_->end(); ++it) {
|
|
const DenseIndex dim = conditional_->getDim(it);
|
|
const Vector contribution = gradientContribution_.segment(position, dim);
|
|
auto [values_it, success] = g->tryInsert(*it, contribution);
|
|
if (!success) values_it->second += contribution;
|
|
position += dim;
|
|
}
|
|
|
|
// Recursively add contributions from children
|
|
for (const auto& child : children) {
|
|
child->addGradientAtZero(g);
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
} // namespace gtsam
|