Added Fixed Lag Smoother and Concurrent Filtering and Smoothing to gtsam_unstable

release/4.3a0
Stephen Williams 2013-02-19 21:37:17 +00:00
parent bc16edd2ac
commit 6fef6cf7d5
15 changed files with 5280 additions and 1 deletions

View File

@ -6,7 +6,7 @@ set (gtsam_unstable_subdirs
discrete
# linear
dynamics
# nonlinear
nonlinear
slam
navigation
)

View File

@ -0,0 +1,563 @@
/* ----------------------------------------------------------------------------
* 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 BatchFixedLagSmoother.cpp
* @brief An LM-based fixed-lag smoother. To the extent possible, this class mimics the iSAM2
* interface. However, additional parameters, such as the smoother lag and the timestamp associated
* with each variable are needed.
*
* @author Michael Kaess, Stephen Williams
* @date Oct 14, 2012
*/
#include <gtsam_unstable/nonlinear/BatchFixedLagSmoother.h>
#include <gtsam/linear/GaussianSequentialSolver.h>
#include <gtsam/linear/GaussianFactorGraph.h>
#include <gtsam/linear/GaussianFactor.h>
#include <gtsam/inference/inference.h>
#include <gtsam/inference/VariableIndex.h>
#include <gtsam/inference/Permutation.h>
#include <gtsam/base/debug.h>
namespace gtsam {
/* ************************************************************************* */
void BatchFixedLagSmoother::PrintKeySet(const std::set<Key>& keys, const std::string& label) {
std::cout << label;
BOOST_FOREACH(gtsam::Key key, keys) {
std::cout << " " << gtsam::DefaultKeyFormatter(key);
}
std::cout << std::endl;
}
/* ************************************************************************* */
void BatchFixedLagSmoother::PrintSymbolicFactor(const GaussianFactor::shared_ptr& factor, const Ordering& ordering) {
std::cout << "f(";
BOOST_FOREACH(Index index, factor->keys()) {
std::cout << " " << index << "[" << gtsam::DefaultKeyFormatter(ordering.key(index)) << "]";
}
std::cout << " )" << std::endl;
}
/* ************************************************************************* */
void BatchFixedLagSmoother::PrintSymbolicGraph(const GaussianFactorGraph& graph, const Ordering& ordering, const std::string& label) {
std::cout << label << std::endl;
BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, graph) {
PrintSymbolicFactor(factor, ordering);
}
}
/* ************************************************************************* */
void BatchFixedLagSmoother::PrintSymbolicFactor(const NonlinearFactor::shared_ptr& factor) {
std::cout << "f(";
if(factor) {
BOOST_FOREACH(Key key, factor->keys()) {
std::cout << " " << gtsam::DefaultKeyFormatter(key);
}
} else {
std::cout << " NULL";
}
std::cout << " )" << std::endl;
}
/* ************************************************************************* */
void BatchFixedLagSmoother::PrintSymbolicGraph(const NonlinearFactorGraph& graph, const std::string& label) {
std::cout << label << std::endl;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, graph) {
PrintSymbolicFactor(factor);
}
}
/* ************************************************************************* */
BatchFixedLagSmoother::BatchFixedLagSmoother(const LevenbergMarquardtParams& params, double smootherLag, bool enforceConsistency) :
parameters_(params), smootherLag_(smootherLag), enforceConsistency_(enforceConsistency) {
}
/* ************************************************************************* */
void BatchFixedLagSmoother::print(const std::string& s, const KeyFormatter& keyFormatter) const {
std::cout << s;
}
/* ************************************************************************* */
bool BatchFixedLagSmoother::equals(const BatchFixedLagSmoother& rhs, double tol) const {
return factors_.equals(rhs.factors_, tol)
&& theta_.equals(rhs.theta_, tol)
&& std::fabs(smootherLag_ - rhs.smootherLag_) < tol
&& std::equal(timestampKeyMap_.begin(), timestampKeyMap_.end(), rhs.timestampKeyMap_.begin());
}
/* ************************************************************************* */
Matrix BatchFixedLagSmoother::marginalR(Key key) const {
// TODO: Fix This
// // Use iSAM2 object to get the marginal factor
// GaussianFactor::shared_ptr marginalGaussian;
// if(params().getFactorization() == "QR")
// marginalGaussian = isam_.marginalFactor(getOrdering().at(key), EliminateQR);
// else if(params().getFactorization() == "CHOLESKY")
// marginalGaussian = isam_.marginalFactor(getOrdering().at(key), EliminateCholesky);
// else
// throw std::invalid_argument(boost::str(boost::format("Encountered unknown factorization type of '%s'. Known types are 'QR' and 'CHOLESKY'.") % params().getFactorization()));
//
// // Extract the information matrix
// JacobianFactor::shared_ptr marginalJacobian = boost::dynamic_pointer_cast<JacobianFactor>(marginalGaussian);
// assert(marginalJacobian != 0);
// return marginalJacobian->getA(marginalJacobian->begin());
return Matrix();
}
/* ************************************************************************* */
Matrix BatchFixedLagSmoother::marginalInformation(Key key) const {
Matrix R(marginalR(key));
return R.transpose() * R;
}
/* ************************************************************************* */
Matrix BatchFixedLagSmoother::marginalCovariance(Key key) const {
Matrix Rinv(inverse(marginalR(key)));
return Rinv * Rinv.transpose();
}
/* ************************************************************************* */
BatchFixedLagSmoother::Result BatchFixedLagSmoother::update(const NonlinearFactorGraph& newFactors, const Values& newTheta, const KeyTimestampMap& timestamps) {
const bool debug = ISDEBUG("BatchFixedLagSmoother update");
if(debug) {
std::cout << "BatchFixedLagSmoother::update() START" << std::endl;
}
// Add the new factors
updateFactors(newFactors);
// Add the new variables
theta_.insert(newTheta);
// Update the Timestamps associated with the factor keys
updateKeyTimestampMap(timestamps);
// Get current timestamp
double current_timestamp = getCurrentTimestamp();
if(debug) std::cout << "Current Timestamp: " << current_timestamp << std::endl;
// Find the set of variables to be marginalized out
std::set<Key> marginalizableKeys = findKeysBefore(current_timestamp - smootherLag_);
if(debug) {
std::cout << "Marginalizable Keys: ";
BOOST_FOREACH(Key key, marginalizableKeys) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << std::endl;
}
// Marginalize out these variables.
// This removes any factors that touch marginalized variables and adds new linear(ized) factors to the graph
marginalizeKeys(marginalizableKeys);
// Create the optimizer
Values linpoint;
linpoint.insert(theta_);
if(enforceConsistency_ && linearizedKeys_.size() > 0) {
linpoint.update(linearizedKeys_);
}
LevenbergMarquardtOptimizer optimizer(factors_, linpoint, parameters_);
// Use a custom optimization loop so the linearization points can be controlled
double currentError;
do {
// Do next iteration
currentError = optimizer.error();
optimizer.iterate();
// Force variables associated with linearized factors to keep the same linearization point
if(enforceConsistency_ && linearizedKeys_.size() > 0) {
// Put the old values of the linearized keys back into the optimizer state
optimizer.state().values.update(linearizedKeys_);
optimizer.state().error = factors_.error(optimizer.state().values);
}
// Maybe show output
if(parameters_.verbosity >= NonlinearOptimizerParams::VALUES) optimizer.values().print("newValues");
if(parameters_.verbosity >= NonlinearOptimizerParams::ERROR) std::cout << "newError: " << optimizer.error() << std::endl;
} while(optimizer.iterations() < parameters_.maxIterations &&
!checkConvergence(parameters_.relativeErrorTol, parameters_.absoluteErrorTol,
parameters_.errorTol, currentError, optimizer.error(), parameters_.verbosity));
// Update the Values from the optimizer
theta_ = optimizer.values();
// Create result structure
Result result;
result.iterations = optimizer.state().iterations;
result.linearVariables = linearizedKeys_.size();
result.nonlinearVariables = theta_.size() - linearizedKeys_.size();
result.error = optimizer.state().error;
if(debug) {
std::cout << "BatchFixedLagSmoother::update() FINISH" << std::endl;
}
return result;
}
/* ************************************************************************* */
void BatchFixedLagSmoother::updateFactors(const NonlinearFactorGraph& newFactors) {
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, newFactors) {
Index index;
// Insert the factor into an existing hole in the factor graph, if possible
if(availableSlots_.size() > 0) {
index = availableSlots_.front();
availableSlots_.pop();
factors_.replace(index, factor);
} else {
index = factors_.size();
factors_.push_back(factor);
}
// Update the FactorIndex
BOOST_FOREACH(Key key, *factor) {
factorIndex_[key].insert(index);
}
}
}
/* ************************************************************************* */
void BatchFixedLagSmoother::removeFactors(const std::set<size_t>& deleteFactors) {
BOOST_FOREACH(size_t slot, deleteFactors) {
if(factors_.at(slot)) {
// Remove references to this factor from the FactorIndex
BOOST_FOREACH(Key key, *(factors_.at(slot))) {
factorIndex_[key].erase(slot);
}
// Remove the factor from the factor graph
factors_.remove(slot);
// Add the factor's old slot to the list of available slots
availableSlots_.push(slot);
} else {
// TODO: Throw an error??
std::cout << "Attempting to remove a factor from slot " << slot << ", but it is already NULL." << std::endl;
}
}
}
/* ************************************************************************* */
void BatchFixedLagSmoother::updateKeyTimestampMap(const KeyTimestampMap& timestamps) {
// Loop through each key and add/update it in the map
BOOST_FOREACH(const KeyTimestampMap::value_type& key_timestamp, timestamps) {
// Check to see if this key already exists inthe database
KeyTimestampMap::iterator keyIter = keyTimestampMap_.find(key_timestamp.first);
// If the key already exists
if(keyIter != keyTimestampMap_.end()) {
// Find the entry in the Timestamp-Key database
std::pair<TimestampKeyMap::iterator,TimestampKeyMap::iterator> range = timestampKeyMap_.equal_range(keyIter->second);
TimestampKeyMap::iterator timeIter = range.first;
while(timeIter->second != key_timestamp.first) {
++timeIter;
}
// remove the entry in the Timestamp-Key database
timestampKeyMap_.erase(timeIter);
// insert an entry at the new time
timestampKeyMap_.insert(TimestampKeyMap::value_type(key_timestamp.second, key_timestamp.first));
// update the Key-Timestamp database
keyIter->second = key_timestamp.second;
} else {
// Add the Key-Timestamp database
keyTimestampMap_.insert(key_timestamp);
// Add the key to the Timestamp-Key database
timestampKeyMap_.insert(TimestampKeyMap::value_type(key_timestamp.second, key_timestamp.first));
}
}
}
/* ************************************************************************* */
double BatchFixedLagSmoother::getCurrentTimestamp() const {
if(timestampKeyMap_.size() > 0) {
return timestampKeyMap_.rbegin()->first;
} else {
return -std::numeric_limits<double>::max();
}
}
/* ************************************************************************* */
std::set<Key> BatchFixedLagSmoother::findKeysBefore(double timestamp) const {
std::set<Key> keys;
TimestampKeyMap::const_iterator end = timestampKeyMap_.lower_bound(timestamp);
for(TimestampKeyMap::const_iterator iter = timestampKeyMap_.begin(); iter != end; ++iter) {
keys.insert(iter->second);
}
return keys;
}
/* ************************************************************************* */
void BatchFixedLagSmoother::eraseKeys(const std::set<Key>& keys) {
// bool debug = true;
// if(debug) std::cout << "BatchFixedLagSmoother::eraseKeys() START" << std::endl;
// if(debug) PrintKeySet(keys, "Keys To Erase: ");
BOOST_FOREACH(Key key, keys) {
// if(debug) std::cout << "Attempting to erase key " << DefaultKeyFormatter(key) << std::endl;
// Erase the key from the Timestamp->Key map
double timestamp = keyTimestampMap_.at(key);
// if(debug) std::cout << "Timestamp associated with key: " << timestamp << std::endl;
TimestampKeyMap::iterator iter = timestampKeyMap_.lower_bound(timestamp);
while(iter != timestampKeyMap_.end() && iter->first == timestamp) {
if(iter->second == key) {
// if(debug) {
// std::cout << "Contents of TimestampKeyMap before Erase:" << std::endl;
// BOOST_FOREACH(const TimestampKeyMap::value_type& timestamp_key, timestampKeyMap_) {
// std::cout << " " << timestamp_key.first << " -> " << DefaultKeyFormatter(timestamp_key.second) << std::endl;
// }
// }
timestampKeyMap_.erase(iter++);
// if(debug) {
// std::cout << "Contents of TimestampKeyMap before Erase:" << std::endl;
// BOOST_FOREACH(const TimestampKeyMap::value_type& timestamp_key, timestampKeyMap_) {
// std::cout << " " << timestamp_key.first << " -> " << DefaultKeyFormatter(timestamp_key.second) << std::endl;
// }
// }
// if(debug) std::cout << "Erased 1 entry from timestampKeyMap_" << std::endl;
} else {
++iter;
}
}
// Erase the key from the Key->Timestamp map
size_t ret;
ret = keyTimestampMap_.erase(key);
// if(debug) std::cout << "Erased " << ret << " entries from keyTimestampMap_" << std::endl;
// Erase the key from the values
theta_.erase(key);
// if(debug) std::cout << "(Hopefully) Erased 1 entries from theta_" << std::endl;
// Erase the key from the factor index
ret = factorIndex_.erase(key);
// if(debug) std::cout << "Erased " << ret << " entries from factorIndex_" << std::endl;
// Erase the key from the set of linearized keys
if(linearizedKeys_.exists(key)) {
linearizedKeys_.erase(key);
// if(debug) std::cout << "Erased 1 entry from linearizedKeys_" << std::endl;
}
}
// if(debug) std::cout << "BatchFixedLagSmoother::eraseKeys() FINISH" << std::endl;
}
/* ************************************************************************* */
struct FactorTree {
std::set<Index> factors;
std::set<Key> keys;
FactorTree(const std::set<Index>& factors, const NonlinearFactorGraph& allFactors) : factors(factors) {
BOOST_FOREACH(const Index& factor, factors) {
BOOST_FOREACH(Key key, *(allFactors.at(factor))) {
keys.insert(key);
}
}
};
void push_back(const FactorTree& factorTree) {
factors.insert(factorTree.factors.begin(), factorTree.factors.end());
keys.insert(factorTree.keys.begin(), factorTree.keys.end());
}
bool hasCommonKeys(Index factor, const NonlinearFactorGraph& allFactors) {
const NonlinearFactor::shared_ptr& f = allFactors.at(factor);
std::set<Key>::const_iterator iter = std::find_first_of(keys.begin(), keys.end(), f->begin(), f->end());
return iter != keys.end();
}
template <class ForwardIterator>
bool hasCommonKeys(ForwardIterator first, ForwardIterator last, const NonlinearFactorGraph& allFactors) {
for(ForwardIterator factor = first; factor != last; ++factor) {
if(hasCommonKeys(*factor, allFactors))
return true;
}
return false;
}
};
/* ************************************************************************* */
void BatchFixedLagSmoother::marginalizeKeys(const std::set<Key>& marginalizableKeys) {
const bool debug = ISDEBUG("BatchFixedLagSmoother update");
if(debug) std::cout << "BatchFixedLagSmoother::marginalizeKeys() START" << std::endl;
// In order to marginalize out the selected variables, the factors involved in those variables
// must be identified and removed from iSAM2. Also, the effect of those removed factors on the
// remaining variables needs to be accounted for. This will be done with linear(ized) factors from
// a partial clique marginalization (or from the iSAM2 cached factor if the entire clique is removed).
// This function finds the set of factors to be removed and generates the linearized factors that
// must be added.
if(marginalizableKeys.size() > 0) {
if(debug) PrintKeySet(marginalizableKeys, "Marginalizable Keys:");
// Find all of the factors associated with marginalizable variables. This set of factors may form a forest.
typedef std::list<FactorTree> FactorForest;
FactorForest factorForest;
BOOST_FOREACH(Key key, marginalizableKeys) {
if(debug) std::cout << "Looking for factors involving key " << DefaultKeyFormatter(key) << std::endl;
// Get the factors associated with this variable
const std::set<size_t>& factors = factorIndex_.at(key);
if(debug) { std::cout << "Found the following factors:" << std::endl; BOOST_FOREACH(size_t i, factors) { std::cout << " "; PrintSymbolicFactor(factors_.at(i)); } }
// Loop over existing factor trees, looking for common keys
std::vector<FactorForest::iterator> commonTrees;
for(FactorForest::iterator tree = factorForest.begin(); tree != factorForest.end(); ++tree) {
if(tree->hasCommonKeys(factors.begin(), factors.end(), factors_)) {
commonTrees.push_back(tree);
}
}
if(debug) std::cout << "Found " << commonTrees.size() << " common trees." << std::endl;
if(commonTrees.size() == 0) {
// No common trees were found. Create a new one.
factorForest.push_back(FactorTree(factors, factors_));
if(debug) std::cout << "Created a new tree." << std::endl;
} else {
// Extract the last common tree
FactorForest::iterator commonTree = commonTrees.back();
commonTrees.pop_back();
// Merge the current factors into this tree
commonTree->push_back(FactorTree(factors, factors_));
// Merge all other common trees into this one, deleting the other trees from the forest.
BOOST_FOREACH(FactorForest::iterator& tree, commonTrees) {
commonTree->push_back(*tree);
factorForest.erase(tree);
}
}
}
if(debug) std::cout << "Found " << factorForest.size() << " factor trees in the set of removed factors." << std::endl;
// For each tree in the forest:
// (0) construct an ordering for the tree
// (1) construct a linear factor graph
// (2) solve for the marginal factors
// (3) convert the marginal factors into Linearized Factors
// (4) remove the marginalized factors from the graph
// (5) add the factors in this tree to the graph
BOOST_FOREACH(const FactorTree& factorTree, factorForest) {
// (0) construct an ordering for this tree
// The ordering should place the marginalizable keys first, then the remaining keys
Ordering ordering;
std::set<Key> marginalizableTreeKeys;
std::set_intersection(factorTree.keys.begin(), factorTree.keys.end(),
marginalizableKeys.begin(), marginalizableKeys.end(),
std::inserter(marginalizableTreeKeys, marginalizableTreeKeys.end()));
std::set<Key> remainingTreeKeys;
std::set_difference(factorTree.keys.begin(), factorTree.keys.end(),
marginalizableTreeKeys.begin(), marginalizableTreeKeys.end(),
std::inserter(remainingTreeKeys, remainingTreeKeys.end()));
// TODO: It may be worthwhile to use CCOLAMD here. (but maybe not???)
BOOST_FOREACH(Key key, marginalizableTreeKeys) {
ordering.push_back(key);
}
BOOST_FOREACH(Key key, remainingTreeKeys) {
ordering.push_back(key);
}
// (1) construct a linear factor graph
GaussianFactorGraph graph;
BOOST_FOREACH(size_t factor, factorTree.factors) {
graph.push_back( factors_.at(factor)->linearize(theta_, ordering) );
}
if(debug) PrintSymbolicGraph(graph, ordering, "Factor Tree:");
// (2) solve for the marginal factors
// Perform partial elimination, resulting in a conditional probability ( P(MarginalizedVariable | RemainingVariables)
// and factors on the remaining variables ( f(RemainingVariables) ). These are the factors we need to add to iSAM2
std::vector<Index> variables;
BOOST_FOREACH(Key key, marginalizableTreeKeys) {
variables.push_back(ordering.at(key));
}
std::pair<GaussianFactorGraph::sharedConditional, GaussianFactorGraph> result = graph.eliminate(variables);
graph = result.second;
if(debug) PrintSymbolicGraph(graph, ordering, "Factors on Remaining Variables:");
// (3) convert the marginal factors into Linearized Factors
NonlinearFactorGraph newFactors;
BOOST_FOREACH(const GaussianFactor::shared_ptr& gaussianFactor, graph) {
// These factors are all generated from BayesNet conditionals. They should all be Jacobians.
JacobianFactor::shared_ptr jacobianFactor = boost::dynamic_pointer_cast<JacobianFactor>(gaussianFactor);
assert(jacobianFactor);
LinearizedGaussianFactor::shared_ptr factor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(jacobianFactor, ordering, getLinearizationPoint()));
// add it to the new factor set
newFactors.push_back(factor);
}
if(debug) std::cout << "1" << std::endl;
// (4) remove the marginalized factors from the graph
removeFactors(factorTree.factors);
if(debug) std::cout << "2" << std::endl;
// (5) add the factors in this tree to the main set of factors
updateFactors(newFactors);
if(debug) std::cout << "3" << std::endl;
// (6) add the keys involved in the linear(ized) factors to the linearizedKey list
FastSet<Key> linearizedKeys = newFactors.keys();
BOOST_FOREACH(Key key, linearizedKeys) {
if(!linearizedKeys_.exists(key)) {
linearizedKeys_.insert(key, theta_.at(key));
}
}
if(debug) std::cout << "4" << std::endl;
}
if(debug) std::cout << "5" << std::endl;
// Store the final estimate of each marginalized key
BOOST_FOREACH(Key key, marginalizableKeys) {
thetaFinal_.insert(key, theta_.at(key));
}
// Remove the marginalized keys from the smoother data structures
eraseKeys(marginalizableKeys);
if(debug) std::cout << "6" << std::endl;
}
if(debug) std::cout << "BatchFixedLagSmoother::marginalizeKeys() FINISH" << std::endl;
}
/* ************************************************************************* */
} /// namespace gtsam

View File

@ -0,0 +1,227 @@
/* ----------------------------------------------------------------------------
* 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 BatchFixedLagSmoother.h
* @brief An LM-based fixed-lag smoother. To the extent possible, this class mimics the iSAM2
* interface. However, additional parameters, such as the smoother lag and the timestamp associated
* with each variable are needed.
*
* @author Michael Kaess, Stephen Williams
* @date Oct 14, 2012
*/
// \callgraph
#pragma once
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <queue>
namespace gtsam {
class BatchFixedLagSmoother {
public:
/// Typedef for a shared pointer to an Incremental Fixed-Lag Smoother
typedef boost::shared_ptr<BatchFixedLagSmoother> shared_ptr;
/// Typedef for a Key-Timestamp map/database
typedef std::map<Key, double> KeyTimestampMap;
typedef std::multimap<double, Key> TimestampKeyMap;
/**
* Meta information returned about the update
*/
// TODO: Think of some more things to put here
struct Result {
size_t iterations; ///< The number of optimizer iterations performed
size_t nonlinearVariables; ///< The number of variables that can be relinearized
size_t linearVariables; ///< The number of variables that must keep a constant linearization point
double error; ///< The final factor graph error
Result() : iterations(0), nonlinearVariables(0), linearVariables(0), error(0) {};
};
/** default constructor */
BatchFixedLagSmoother(const LevenbergMarquardtParams& parameters = LevenbergMarquardtParams(), double smootherLag = 0.0, bool enforceConsistency = false);
/** destructor */
virtual ~BatchFixedLagSmoother() { };
// TODO: Create a better 'print'
/** Print the factor for debugging and testing (implementing Testable) */
virtual void print(const std::string& s = "BatchFixedLagSmoother:\n", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
/** Check if two IncrementalFixedLagSmoother Objects are equal */
virtual bool equals(const BatchFixedLagSmoother& rhs, double tol = 1e-9) const;
/**
* Add new factors, updating the solution and relinearizing as needed.
*/
Result update(const NonlinearFactorGraph& newFactors = NonlinearFactorGraph(), const Values& newTheta = Values(), const KeyTimestampMap& timestamps = KeyTimestampMap());
/** Access the current set of timestamps associated with each variable */
const KeyTimestampMap& getTimestamps() const {
return keyTimestampMap_;
}
/** Access the current linearization point */
const Values& getLinearizationPoint() const {
return theta_;
}
/** Compute an estimate from the incomplete linear delta computed during the last update.
* This delta is incomplete because it was not updated below wildfire_threshold. If only
* a single variable is needed, it is faster to call calculateEstimate(const KEY&).
*/
Values calculateEstimate() const {
//return theta_.retract(delta_, ordering_);
return theta_;
}
/** Compute an estimate for a single variable using its incomplete linear delta computed
* during the last update. This is faster than calling the no-argument version of
* calculateEstimate, which operates on all variables.
* @param key
* @return
*/
template<class VALUE>
VALUE calculateEstimate(Key key) const {
//const Index index = ordering_.at(key);
//const SubVector delta = delta_.at(index);
//return theta_.at<VALUE>(key).retract(delta);
return theta_.at<VALUE>(key);
}
/** Compute an estimate using a complete delta computed by a full back-substitution.
*/
Values calculateBestEstimate() const {
return calculateEstimate();
}
/** Return the final/best estimate of each variable encountered, including those that have been marginalized out previously.
*/
Values calculateFinalEstimate() const {
Values final(thetaFinal_);
final.insert(theta_);
return final;
}
/** Access the set of nonlinear factors */
const NonlinearFactorGraph& getFactorsUnsafe() const {
return factors_;
}
/** return the current set of iSAM2 parameters */
const LevenbergMarquardtParams& params() const {
return parameters_;
}
/** return the current smoother lag */
double smootherLag() const {
return smootherLag_;
}
/** return the marginal square root information matrix associated with a specific variable */
Matrix marginalR(Key key) const;
/** return the marginal information matrix associated with a specific variable */
Matrix marginalInformation(Key key) const;
/** return the marginal covariance associated with a specific variable */
Matrix marginalCovariance(Key key) const;
protected:
/** The nonlinear factors **/
NonlinearFactorGraph factors_;
/** The current linearization point **/
Values theta_;
/** The final estimate of each variable **/
Values thetaFinal_;
/** The L-M optimization parameters **/
LevenbergMarquardtParams parameters_;
/** The length of the smoother lag. Any variable older than this amount will be marginalized out. */
double smootherLag_;
/** A flag indicating if the optimizer should enforce probabilistic consistency by maintaining the
* linearization point of all variables involved in linearized/marginal factors at the edge of the
* smoothing window. This idea is from ??? TODO: Look up paper reference **/
bool enforceConsistency_;
/** The current timestamp associated with each tracked key */
TimestampKeyMap timestampKeyMap_;
KeyTimestampMap keyTimestampMap_;
/** A typedef defining an Key-Factor mapping **/
typedef std::map<Key, std::set<Index> > FactorIndex;
/** A cross-reference structure to allow efficient factor lookups by key **/
FactorIndex factorIndex_;
/** The set of keys involved in current linearized factors. These keys should not be relinearized. **/
//std::set<Key> linearizedKeys_;
Values linearizedKeys_;
/** The set of available factor graph slots. These occur because we are constantly deleting factors, leaving holes. **/
std::queue<size_t> availableSlots_;
/** Augment the list of factors with a set of new factors */
void updateFactors(const NonlinearFactorGraph& newFactors);
/** Remove factors from the list of factors by slot index */
void removeFactors(const std::set<size_t>& deleteFactors);
/** Update the Timestamps associated with the keys */
void updateKeyTimestampMap(const KeyTimestampMap& newTimestamps);
/** Find the most recent timestamp of the system */
double getCurrentTimestamp() const;
/** Find all of the keys associated with timestamps before the provided time */
std::set<Key> findKeysBefore(double timestamp) const;
/** Erase any keys associated with timestamps before the provided time */
void eraseKeys(const std::set<Key>& keys);
/** Marginalize out selected variables */
void marginalizeKeys(const std::set<Key>& marginalizableKeys);
private:
static void PrintKeySet(const std::set<Key>& keys, const std::string& label);
static void PrintSymbolicFactor(const GaussianFactor::shared_ptr& factor, const Ordering& ordering);
static void PrintSymbolicGraph(const GaussianFactorGraph& graph, const Ordering& ordering, const std::string& label);
static void PrintSymbolicFactor(const NonlinearFactor::shared_ptr& factor);
static void PrintSymbolicGraph(const NonlinearFactorGraph& graph, const std::string& label);
}; // BatchFixedLagSmoother
} /// namespace gtsam

View File

@ -0,0 +1,916 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentBatchFilter.cpp
* @brief A Levenberg-Marquardt Batch Filter that implements the
* Concurrent Filtering and Smoothing interface.
* @author Stephen Williams
*/
#include <gtsam_unstable/nonlinear/ConcurrentBatchFilter.h>
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/inference/JunctionTree.h>
#include <gtsam/base/timing.h>
#include <boost/lambda/lambda.hpp>
#include <gtsam/linear/GaussianMultifrontalSolver.h>
namespace gtsam {
/* ************************************************************************* */
void ConcurrentBatchFilter::SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent) {
std::cout << indent << "P( ";
BOOST_FOREACH(Index index, clique->conditional()->frontals()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
if(clique->conditional()->nrParents() > 0) {
std::cout << "| ";
}
BOOST_FOREACH(Index index, clique->conditional()->parents()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
BOOST_FOREACH(const Clique& child, clique->children()) {
SymbolicPrintTree(child, ordering, indent+" ");
}
}
/* ************************************************************************* */
void ConcurrentBatchFilter::print(const std::string& s, const KeyFormatter& keyFormatter) const {
std::cout << s;
graph_.print("Factors:\n");
theta_.print("Values:\n");
}
/* ************************************************************************* */
ConcurrentBatchFilter::Result ConcurrentBatchFilter::update(const NonlinearFactorGraph& newFactors, const Values& newTheta, const KeyTimestampMap& timestamps) {
gttic(update);
// Create the return result meta-data
Result result;
gttic(augment_system);
// Add the new factors to the graph
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, newFactors) {
insertFactor(factor);
}
// Add the new variables to theta
theta_.insert(newTheta);
// Update the Timestamps associated with the factor keys
updateKeyTimestampMap(timestamps);
gttoc(augment_system);
// Optimize the graph, updating theta
gttic(optimize);
if(graph_.size() > 0) {
// Create an L-M optimizer
Values linpoint;
linpoint.insert(theta_);
if(separatorValues_.size() > 0) {
linpoint.update(separatorValues_);
}
LevenbergMarquardtOptimizer optimizer(graph_, linpoint, parameters_);
// Use a custom optimization loop so the linearization points can be controlled
double currentError;
do {
// Do next iteration
gttic(optimizer_iteration);
currentError = optimizer.error();
optimizer.iterate();
gttoc(optimizer_iteration);
// Force variables associated with root keys to keep the same linearization point
gttic(enforce_consistency);
if(separatorValues_.size() > 0) {
// Put the old values of the root keys back into the optimizer state
optimizer.state().values.update(separatorValues_);
optimizer.state().error = graph_.error(optimizer.state().values);
}
gttoc(enforce_consistency);
// Maybe show output
if(parameters_.verbosity >= NonlinearOptimizerParams::VALUES) optimizer.values().print("newValues");
if(parameters_.verbosity >= NonlinearOptimizerParams::ERROR) std::cout << "newError: " << optimizer.error() << std::endl;
} while(optimizer.iterations() < parameters_.maxIterations &&
!checkConvergence(parameters_.relativeErrorTol, parameters_.absoluteErrorTol,
parameters_.errorTol, currentError, optimizer.error(), parameters_.verbosity));
// Update the Values from the optimizer
theta_ = optimizer.values();
result.iterations = optimizer.state().iterations;
result.nonlinearVariables = theta_.size() - separatorValues_.size();
result.linearVariables = separatorValues_.size();
result.error = optimizer.state().error;
}
gttoc(optimize);
gttoc(update);
return result;
}
/* ************************************************************************* */
void ConcurrentBatchFilter::presync() {
gttic(presync);
gttoc(presync);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::synchronize(const NonlinearFactorGraph& summarizedFactors) {
gttic(synchronize);
std::cout << "ConcurrentBatchFilter::synchronize(*) Begin" << std::endl;
//// Check that solution before marginalization matches solution after marginalization
//Values solutionBefore;
//solutionBefore.insert(theta_);
//solutionBefore.update(smootherSeparatorValues_);
//Ordering orderingBefore = *graph_.orderingCOLAMD(solutionBefore);
//GaussianFactorGraph lingraphBefore = *graph_.linearize(solutionBefore, orderingBefore);
//GaussianMultifrontalSolver gmsBefore(lingraphBefore, true);
//VectorValues deltaBefore = *gmsBefore.optimize();
//solutionBefore = solutionBefore.retract(deltaBefore, orderingBefore);
std::cout << "ConcurrentBatchFilter::synchronize(*) Input Smoother Summarization:" << std::endl;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, summarizedFactors) {
std::cout << " f( ";
BOOST_FOREACH(Key key, factor->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
// At this step, we need to update the summarized factors representing the smoother,
// then update the solution by re-eliminating the nonlinear graph. In the process, we
// will create a Bayes Tree and form a new root clique. We will let CCOLAMD define
// the ordering, constraining the most recent variables (variables > lag) to the
// top of the tree.
//
// Once formed, we then need to determine which branches will be sent to the smoother,
// which branches constitute the filter, and which clique(s) form the root.
//
// Ideally, all of the old variables will be in the smoother. But, depending on the
// factors and the ordering, there may be some cliques with recent and old variables.
// So, we define the smoother branches as those remaining after removing any clique
// with any recent variables.
//
// The root should be the smallest set of variables that separate the smoother from
// the rest of the graph. Thus, the root will be the parents of all the smoother
// branches (plus the path to the root to ensure a connected, full-rank system).
//
// The filter branches are then defined as the children of the root that are not
// smoother branches.
//std::cout << "Previous Graph:" << std::endl;
//for(size_t i = 0; i < graph_.size(); ++i) {
// if(graph_[i]) {
// std::cout << " f" << i << "( ";
// BOOST_FOREACH(Key key, graph_[i]->keys()) {
// std::cout << DefaultKeyFormatter(key) << " ";
// }
// std::cout << ")" << std::endl;
// }
//}
// TODO: Temporarily remove smoother effects
// Remove the previous smoother summarization from the graph
BOOST_FOREACH(size_t slot, smootherSummarizationSlots_) {
removeFactor(slot);
}
smootherSummarizationSlots_.clear();
std::cout << "Removed Previous Smoother Summarization:" << std::endl;
for(size_t i = 0; i < graph_.size(); ++i) {
if(graph_[i]) {
std::cout << " f" << i << "( ";
BOOST_FOREACH(Key key, graph_[i]->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
}
// TODO: Temporarily remove smoother effects
// Insert the updated smoother summarized factors
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, summarizedFactors) {
smootherSummarizationSlots_.push_back(insertFactor(factor));
}
std::cout << "Added New Smoother Summarization:" << std::endl;
for(size_t i = 0; i < graph_.size(); ++i) {
if(graph_[i]) {
std::cout << " f" << i << "( ";
BOOST_FOREACH(Key key, graph_[i]->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
}
// TODO: Temporarily remove smoother effects
// Now that the smoother summarization has been updated, re-optimize the filter
update();
// The filter should now be optimal (up to the linearization point of the separator variables)
// Calculate a new separator, a new filter summarization, a new smoother summarization, and the
// set of factors to send to the smoother
// Force variables associated with root keys to keep the same linearization point
// TODO: This may be too many variables. It may only require that the smoother separator keys be kept constant.
gttic(enforce_consistency);
Values linpoint = theta_;
if(separatorValues_.size() > 0) {
linpoint.update(separatorValues_);
}
gttoc(enforce_consistency);
std::cout << "ConcurrentBatchFilter::synchronize(*) Old Smoother Separator Keys: ";
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, separatorValues_) {
std::cout << DefaultKeyFormatter(key_value.key) << " ";
}
std::cout << std::endl;
//std::cout << "ConcurrentBatchFilter::synchronize(*) Old Root Keys: ";
//BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) {
// std::cout << DefaultKeyFormatter(key_value.key) << " ";
//}
//std::cout << std::endl;
//std::cout << "ConcurrentBatchFilter::synchronize(*) All Keys: ";
//BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, linpoint) {
// std::cout << DefaultKeyFormatter(key_value.key) << " ";
//}
//std::cout << std::endl;
// Calculate an ordering that puts variables newer than LAG at the top
gttic(compute_ordering);
std::set<Key> recentKeys = findKeysAfter(getCurrentTimestamp() - lag_);
std::map<Key, int> constraints;
BOOST_FOREACH(Key key, recentKeys) {
constraints[key] = 1;
}
Ordering ordering = *graph_.orderingCOLAMDConstrained(linpoint, constraints);
gttoc(compute_ordering);
//typedef std::map<Key, int> Constraints;
//std::cout << "ConcurrentBatchFilter::synchronize(*) Constraints: ";
//BOOST_FOREACH(const Constraints::value_type key_group, constraints) {
// std::cout << DefaultKeyFormatter(key_group.first) << " ";
//}
//std::cout << std::endl;
//ordering.print("ConcurrentBatchFilter::synchronize(*) Ordering:\n");
// Create a Bayes Tree using iSAM2 cliques
gttic(create_bayes_tree);
JunctionTree<GaussianFactorGraph, ISAM2Clique> jt(*graph_.linearize(linpoint, ordering));
ISAM2Clique::shared_ptr root = jt.eliminate(parameters_.getEliminationFunction());
BayesTree<GaussianConditional, ISAM2Clique> bayesTree;
bayesTree.insert(root);
gttoc(create_bayes_tree);
//std::cout << "ConcurrentBatchFilter::synchronize(*) Create Bayes Tree:" << std::endl;
//SymbolicPrintTree(root, ordering, " ");
// Find all of the filter cliques, and the smoother branches
// The smoother branches are defined as the children filter cliques that are not also filter cliques
gttic(identify_smoother_branches);
std::set<ISAM2Clique::shared_ptr> filterCliques;
std::set<ISAM2Clique::shared_ptr> smootherBranches;
BOOST_FOREACH(Key key, recentKeys) {
Index index = ordering.at(key);
const ISAM2Clique::shared_ptr& clique = bayesTree.nodes().at(index);
if(clique) {
// Add all of the child-cliques to the smoother branches
filterCliques.insert(clique);
smootherBranches.insert(clique->children().begin(), clique->children().end());
}
}
BOOST_FOREACH(const ISAM2Clique::shared_ptr& filterClique, filterCliques) {
smootherBranches.erase(filterClique);
}
gttoc(identify_smoother_branches);
std::cout << "ConcurrentBatchFilter::synchronize(*) Smoother Branches:" << std::endl;
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
std::cout << " P( ";
BOOST_FOREACH(Index index, clique->conditional()->frontals()) {
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
if(clique->conditional()->nrParents() > 0) { std::cout << "| "; }
BOOST_FOREACH(Index index, clique->conditional()->parents()) {
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
}
// Find all of the frontal keys in the smoother branches
gttic(find_smoother_keys);
std::set<Key> smootherKeys;
std::queue<ISAM2Clique::shared_ptr> smootherCliques;
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
smootherCliques.push(clique);
}
while(smootherCliques.size() > 0) {
// Extract the frontal keys from the next clique
BOOST_FOREACH(Index index, smootherCliques.front()->conditional()->frontals()) {
smootherKeys.insert(ordering.key(index));
}
// Add any children to the queue
BOOST_FOREACH(const ISAM2Clique::shared_ptr& child, smootherCliques.front()->children()) {
smootherCliques.push(child);
}
// Remove this clique from the queue
smootherCliques.pop();
}
// Store the current linearization point for future transmission to the smoother (skip any variables in the previous root)
smootherValues_.clear();
BOOST_FOREACH(Key key, smootherKeys) {
smootherValues_.insert(key, linpoint.at(key));
}
gttoc(find_smoother_keys);
std::cout << "ConcurrentBatchFilter::synchronize(*) Smoother Keys: ";
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, smootherValues_) {
std::cout << DefaultKeyFormatter(key_value.key) << " ";
}
std::cout << std::endl;
// Find the set of smoother separator keys
// The separator keys are defined as the set of parents to current branches
// + the parents of previous branches that are not new smoother keys
gttic(update_smoother_separator);
BOOST_FOREACH(Key key, smootherKeys) {
if(separatorValues_.exists(key)) {
separatorValues_.erase(key);
}
}
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
BOOST_FOREACH(Index index, clique->conditional()->parents()) {
Key key = ordering.key(index);
if(!separatorValues_.exists(key))
separatorValues_.insert(key, linpoint.at(key));
}
}
gttoc(update_smoother_separator);
std::cout << "ConcurrentBatchFilter::synchronize(*) New Smoother Separator Keys: ";
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, separatorValues_) {
std::cout << DefaultKeyFormatter(key_value.key) << " ";
}
std::cout << std::endl;
// Calculate the new smoother summarization.
// This is potentially a combination of the previous summarization and cached factors from new smoother branches
gttic(extract_smoother_summarization);
// Add in previous summarization, marginalizing out non-smoother-separator keys
std::set<Key> smootherSeparatorKeys;
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, separatorValues_) {
smootherSeparatorKeys.insert(key_value.key);
}
NonlinearFactorGraph smootherSummarization;
BOOST_FOREACH(size_t slot, smootherSummarizationSlots_) {
if(smootherBranches.size() == 0) {
graph_.at(slot)->print("Summarized Factor Before Marginalization:\n");
NonlinearFactor::shared_ptr marginalFactor = marginalizeKeysFromFactor(graph_.at(slot), smootherSeparatorKeys);
if(marginalFactor) {
smootherSummarization.push_back(marginalFactor);
}
if(marginalFactor) {
marginalFactor->print("Summarized Factor After Marginalization:\n");
} else {
std::cout << "Summarized Factor After Marginalization:\n{NULL}" << std::endl;
}
}
removeFactor(slot);
}
smootherSummarizationSlots_.clear();
// Insert cached factors for any smoother branches
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
clique->cachedFactor()->print("Summarized Cached Factor:\n");
LinearizedGaussianFactor::shared_ptr factor;
if(const JacobianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<JacobianFactor>(clique->cachedFactor()))
factor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(rhs, ordering, linpoint));
else if(const HessianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<HessianFactor>(clique->cachedFactor()))
factor = LinearizedHessianFactor::shared_ptr(new LinearizedHessianFactor(rhs, ordering, linpoint));
else
throw std::invalid_argument("In ConcurrentBatchFilter::process(...), cached factor is neither a JacobianFactor nor a HessianFactor");
factor->print("Summarized Linearized Factor:\n");
smootherSummarization.push_back(factor);
}
gttoc(extract_smoother_summarization);
std::cout << "ConcurrentBatchFilter::synchronize(*) Smoother Summarization:" << std::endl;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, smootherSummarization) {
if(boost::dynamic_pointer_cast<LinearizedGaussianFactor>(factor))
std::cout << " L( ";
else
std::cout << " f( ";
BOOST_FOREACH(Key key, factor->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
// Find all of the factors that contain at least one smoother key
// These nonlinear factors will be sent to the smoother
gttic(find_smoother_factors);
std::set<size_t> smootherFactorSlots = findFactorsWithAny(smootherKeys);
// Convert the set of slots into a set of factors
smootherFactors_.resize(0);
BOOST_FOREACH(size_t slot, smootherFactorSlots) {
smootherFactors_.push_back(graph_.at(slot));
}
gttoc(find_smoother_factors);
std::cout << "ConcurrentBatchFilter::synchronize(*) Smoother Factors:" << std::endl;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, smootherFactors_) {
if(boost::dynamic_pointer_cast<LinearizedGaussianFactor>(factor))
std::cout << " L( ";
else
std::cout << " f( ";
BOOST_FOREACH(Key key, factor->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
// Find the Root Cliques
// The root is defined as the parents of the smoother separator + the path to the root
gttic(identify_root_cliques);
std::set<ISAM2Clique::shared_ptr> rootCliques;
BOOST_FOREACH(Key key, smootherSeparatorKeys) {
Index index = ordering.at(key);
const ISAM2Clique::shared_ptr& clique = bayesTree.nodes().at(index);
if(clique) {
rootCliques.insert(clique);
}
}
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, rootCliques) {
ISAM2Clique::shared_ptr rootClique = clique;
while(!rootClique->isRoot()) {
rootClique = rootClique->parent();
rootCliques.insert(rootClique);
}
}
std::set<Key> rootKeys;
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, rootCliques) {
BOOST_FOREACH(Index index, clique->conditional()->frontals()) {
rootKeys.insert(ordering.key(index));
}
}
gttoc(identify_root_cliques);
//std::cout << "ConcurrentBatchFilter::synchronize(*) Root Cliques:" << std::endl;
//BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, rootCliques) {
//std::cout << " P( ";
//BOOST_FOREACH(Index index, clique->conditional()->frontals()) {
// std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
//}
//if(clique->conditional()->nrParents() > 0) { std::cout << "| "; }
//BOOST_FOREACH(Index index, clique->conditional()->parents()) {
// std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
//}
//std::cout << ")" << std::endl;
//}
// Update the keys and linearization points of the new root
gttic(update_root_values);
rootValues_.clear();
BOOST_FOREACH(Key key, rootKeys) {
rootValues_.insert(key, linpoint.at(key));
}
gttoc(update_root_values);
//std::cout << "ConcurrentBatchFilter::synchronize(*) New Root Keys: ";
//BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) {
// std::cout << DefaultKeyFormatter(key_value.key) << " ";
//}
//std::cout << std::endl;
// Identify the filter branches
// The filter branches are the children of the root that are not smoother branches.
gttic(identify_filter_branches);
std::set<ISAM2Clique::shared_ptr> filterBranches;
// Find the children of the root
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, rootCliques) {
filterBranches.insert(clique->children().begin(), clique->children().end());
}
// Remove any root cliques that made it into the filter branches
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, rootCliques) {
filterBranches.erase(clique);
}
// Remove the smoother branches
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
filterBranches.erase(clique);
}
gttoc(identify_filter_branches);
//std::cout << "ConcurrentBatchFilter::synchronize(*) Filter Branches:" << std::endl;
//BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, filterBranches) {
// std::cout << " P( ";
// BOOST_FOREACH(Index index, clique->conditional()->frontals()) {
// std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
// }
// if(clique->conditional()->nrParents() > 0) { std::cout << "| "; }
// BOOST_FOREACH(Index index, clique->conditional()->parents()) {
// std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
// }
// std::cout << ")" << std::endl;
//}
// Extract cached factors from the filter branches and store for future transmission to the smoother
gttic(extract_filter_summarization);
filterSummarization_.resize(0);
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, filterBranches) {
LinearizedGaussianFactor::shared_ptr factor;
if(const JacobianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<JacobianFactor>(clique->cachedFactor()))
factor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(rhs, ordering, linpoint));
else if(const HessianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<HessianFactor>(clique->cachedFactor()))
factor = LinearizedHessianFactor::shared_ptr(new LinearizedHessianFactor(rhs, ordering, linpoint));
else
throw std::invalid_argument("In ConcurrentBatchFilter::process(...), cached factor is neither a JacobianFactor nor a HessianFactor");
filterSummarization_.push_back(factor);
}
gttoc(extract_filter_summarization);
// Find all factors that contain only root clique variables
// This information is not contained in either the smoother
// summarization or the filter summarization. This must be sent
// to the smoother as well. Including it as part of the
// root/filter summarization
gttic(extract_root_summarization);
std::set<size_t> rootFactorSlots = findFactorsWithOnly(rootKeys);
BOOST_FOREACH(size_t slot, rootFactorSlots) {
filterSummarization_.push_back(graph_.at(slot));
}
gttoc(extract_root_summarization);
//std::cout << "ConcurrentBatchFilter::synchronize(*) Filter Summarization:" << std::endl;
//BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, filterSummarization_) {
// if(boost::dynamic_pointer_cast<LinearizedGaussianFactor>(factor))
// std::cout << " L( ";
// else
// std::cout << " f( ";
// BOOST_FOREACH(Key key, factor->keys()) {
// std::cout << DefaultKeyFormatter(key) << " ";
// }
// std::cout << ")" << std::endl;
//}
gttic(purge_smoother_information);
// Remove nonlinear factors being sent to the smoother
BOOST_FOREACH(size_t slot, smootherFactorSlots) {
removeFactor(slot);
}
// Remove the nonlinear keys being sent to the smoother
BOOST_FOREACH(Key key, smootherKeys) {
removeKey(key);
}
// Add all of the smoother summarization factors to the filter, keeping track of their locations for later removal
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, smootherSummarization) {
smootherSummarizationSlots_.push_back(insertFactor(factor));
}
gttoc(purge_smoother_information);
// // Check that solution before marginalization matches solution after marginalization
// Values solutionAfter;
// solutionAfter.insert(theta_);
// solutionAfter.update(smootherSeparatorValues_);
// Ordering orderingAfter = *graph_.orderingCOLAMD(solutionAfter);
// GaussianFactorGraph lingraphAfter = *graph_.linearize(solutionAfter, orderingAfter);
// GaussianMultifrontalSolver gmsAfter(lingraphAfter, true);
// VectorValues deltaAfter = *gmsAfter.optimize();
// solutionAfter = solutionAfter.retract(deltaAfter, orderingAfter);
//
// BOOST_FOREACH(Key key, smootherKeys) {
// solutionBefore.erase(key);
// }
//
//std::cout << "Solution before marginalization matches solution after marginalization: " << solutionBefore.equals(solutionAfter, 1.0e-9) << std::endl;
std::cout << "ConcurrentBatchFilter::synchronize(*) End" << std::endl;
gttoc(synchronize);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::getSummarizedFactors(NonlinearFactorGraph& summarizedFactors, Values& rootValues) {
gttic(get_summarized_factors);
// Copy the previous calculated smoother summarization factors into the output
summarizedFactors.push_back(filterSummarization_);
rootValues.insert(rootValues_);
gttic(get_summarized_factors);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::getSmootherFactors(NonlinearFactorGraph& smootherFactors, Values& smootherValues) {
gttic(get_smoother_factors);
// Copy the previous calculated smoother summarization factors into the output
smootherFactors.push_back(smootherFactors_);
smootherValues.insert(smootherValues_);
gttic(get_smoother_factors);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::postsync() {
gttic(postsync);
gttoc(postsync);
}
/* ************************************************************************* */
size_t ConcurrentBatchFilter::insertFactor(const NonlinearFactor::shared_ptr& factor) {
gttic(insert_factors);
// Insert the factor into an existing hole in the factor graph, if possible
size_t slot;
if(availableSlots_.size() > 0) {
slot = availableSlots_.front();
availableSlots_.pop();
graph_.replace(slot, factor);
} else {
slot = graph_.size();
graph_.push_back(factor);
}
// Update the FactorIndex
BOOST_FOREACH(Key key, *factor) {
factorIndex_[key].insert(slot);
}
gttoc(insert_factors);
return slot;
}
/* ************************************************************************* */
void ConcurrentBatchFilter::removeFactor(size_t slot) {
gttic(remove_factors);
// Remove references to this factor from the FactorIndex
BOOST_FOREACH(Key key, *(graph_.at(slot))) {
factorIndex_[key].erase(slot);
}
// Remove this factor from the graph
graph_.remove(slot);
// Mark the factor slot as avaiable
availableSlots_.push(slot);
gttoc(remove_factors);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::removeKey(Key key) {
gttic(remove_keys);
// Erase the key from the Timestamp->Key map
double timestamp = keyTimestampMap_.at(key);
TimestampKeyMap::iterator iter = timestampKeyMap_.lower_bound(timestamp);
while(iter != timestampKeyMap_.end() && iter->first == timestamp) {
if(iter->second == key) {
timestampKeyMap_.erase(iter++);
} else {
++iter;
}
}
// Erase the key from the Key->Timestamp map
keyTimestampMap_.erase(key);
// Erase the key from the values
theta_.erase(key);
// Erase the key from the set of linearized keys
// TODO: Should this ever even occur?
if(rootValues_.exists(key)) {
rootValues_.erase(key);
}
gttoc(remove_keys);
}
/* ************************************************************************* */
void ConcurrentBatchFilter::updateKeyTimestampMap(const KeyTimestampMap& timestamps) {
gttic(update_key_timestamp_map);
// Loop through each key and add/update it in the map
BOOST_FOREACH(const KeyTimestampMap::value_type& key_timestamp, timestamps) {
// Check to see if this key already exists inthe database
KeyTimestampMap::iterator keyIter = keyTimestampMap_.find(key_timestamp.first);
// If the key already exists
if(keyIter != keyTimestampMap_.end()) {
// Find the entry in the Timestamp-Key database
std::pair<TimestampKeyMap::iterator,TimestampKeyMap::iterator> range = timestampKeyMap_.equal_range(keyIter->second);
TimestampKeyMap::iterator timeIter = range.first;
while(timeIter->second != key_timestamp.first) {
++timeIter;
}
// remove the entry in the Timestamp-Key database
timestampKeyMap_.erase(timeIter);
// insert an entry at the new time
timestampKeyMap_.insert(TimestampKeyMap::value_type(key_timestamp.second, key_timestamp.first));
// update the Key-Timestamp database
keyIter->second = key_timestamp.second;
} else {
// Add the Key-Timestamp database
keyTimestampMap_.insert(key_timestamp);
// Add the key to the Timestamp-Key database
timestampKeyMap_.insert(TimestampKeyMap::value_type(key_timestamp.second, key_timestamp.first));
}
}
gttoc(update_key_timestamp_map);
}
/* ************************************************************************* */
double ConcurrentBatchFilter::getCurrentTimestamp() const {
gttic(get_current_timestamp);
double timestamp;
if(timestampKeyMap_.size() > 0) {
timestamp = timestampKeyMap_.rbegin()->first;
} else {
timestamp = -std::numeric_limits<double>::max();
}
gttoc(get_current_timestamp);
return timestamp;
}
/* ************************************************************************* */
std::set<Key> ConcurrentBatchFilter::findKeysBefore(double timestamp) const {
gttic(find_keys_before);
std::set<Key> keys;
TimestampKeyMap::const_iterator end = timestampKeyMap_.lower_bound(timestamp);
for(TimestampKeyMap::const_iterator iter = timestampKeyMap_.begin(); iter != end; ++iter) {
keys.insert(iter->second);
}
gttoc(find_keys_before);
return keys;
}
/* ************************************************************************* */
std::set<Key> ConcurrentBatchFilter::findKeysAfter(double timestamp) const {
gttic(find_keys_after);
std::set<Key> keys;
TimestampKeyMap::const_iterator begin = timestampKeyMap_.lower_bound(timestamp);
for(TimestampKeyMap::const_iterator iter = begin; iter != timestampKeyMap_.end(); ++iter) {
keys.insert(iter->second);
}
gttoc(find_keys_after);
return keys;
}
/* ************************************************************************* */
std::set<size_t> ConcurrentBatchFilter::findFactorsWithAny(const std::set<Key>& keys) const {
// Find the set of factor slots for each specified key
std::set<size_t> factorSlots;
BOOST_FOREACH(Key key, keys) {
FactorIndex::const_iterator iter = factorIndex_.find(key);
if(iter != factorIndex_.end()) {
factorSlots.insert(iter->second.begin(), iter->second.end());
}
}
return factorSlots;
}
/* ************************************************************************* */
std::set<size_t> ConcurrentBatchFilter::findFactorsWithOnly(const std::set<Key>& keys) const {
// Find the set of factor slots with any of the provided keys
std::set<size_t> factorSlots = findFactorsWithAny(keys);
// Test each factor for non-specified keys
std::set<size_t>::iterator slot = factorSlots.begin();
while(slot != factorSlots.end()) {
const NonlinearFactor::shared_ptr& factor = graph_.at(*slot);
std::set<Key> factorKeys(factor->begin(), factor->end()); // ensure the keys are sorted
if(!std::includes(keys.begin(), keys.end(), factorKeys.begin(), factorKeys.end())) {
factorSlots.erase(slot++);
} else {
++slot;
}
}
return factorSlots;
}
/* ************************************************************************* */
NonlinearFactor::shared_ptr ConcurrentBatchFilter::marginalizeKeysFromFactor(const NonlinearFactor::shared_ptr& factor, const std::set<Key>& remainingKeys) const {
const bool debug = false;
if(debug) std::cout << "ConcurrentBatchFilter::marginalizeKeys() START" << std::endl;
LinearizedGaussianFactor::shared_ptr linearizedFactor = boost::dynamic_pointer_cast<LinearizedGaussianFactor>(factor);
assert(linearizedFactor);
// Sort the keys for this factor
std::set<Key> factorKeys;
BOOST_FOREACH(Key key, *linearizedFactor) {
factorKeys.insert(key);
}
// Calculate the set of keys to marginalize
std::set<Key> marginalizeKeys;
std::set_difference(factorKeys.begin(), factorKeys.end(), remainingKeys.begin(), remainingKeys.end(), std::inserter(marginalizeKeys, marginalizeKeys.end()));
//
if(marginalizeKeys.size() == 0) {
// No keys need to be marginalized out. Simply return the original factor.
return linearizedFactor;
} else if(marginalizeKeys.size() == linearizedFactor->size()) {
// All keys need to be marginalized out. Return an empty factor
return NonlinearFactor::shared_ptr();
} else {
// (0) Create an ordering with the remaining keys last
Ordering ordering;
BOOST_FOREACH(Key key, marginalizeKeys) {
ordering.push_back(key);
}
BOOST_FOREACH(Key key, remainingKeys) {
ordering.push_back(key);
}
// (1) construct a linear factor graph
GaussianFactorGraph graph;
graph.push_back( linearizedFactor->linearize(linearizedFactor->linearizationPoint(), ordering) );
// (2) solve for the marginal factor
// Perform partial elimination, resulting in a conditional probability ( P(MarginalizedVariable | RemainingVariables)
// and factors on the remaining variables ( f(RemainingVariables) ). These are the factors we need to add to iSAM2
std::vector<Index> variables;
BOOST_FOREACH(Key key, marginalizeKeys) {
variables.push_back(ordering.at(key));
}
std::pair<GaussianFactorGraph::sharedConditional, GaussianFactorGraph> result = graph.eliminate(variables);
graph = result.second;
// (3) convert the marginal factors into Linearized Factors
NonlinearFactor::shared_ptr marginalFactor;
assert(graph.size() <= 1);
if(graph.size() > 0) {
// These factors are all generated from BayesNet conditionals. They should all be Jacobians.
JacobianFactor::shared_ptr jacobianFactor = boost::dynamic_pointer_cast<JacobianFactor>(graph.at(0));
assert(jacobianFactor);
marginalFactor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(jacobianFactor, ordering, linearizedFactor->linearizationPoint()));
}
return marginalFactor;
}
}
/* ************************************************************************* */
}/// namespace gtsam

View File

@ -0,0 +1,195 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentBatchFilter.h
* @brief A Levenberg-Marquardt Batch Filter that implements the
* Concurrent Filtering and Smoothing interface.
* @author Stephen Williams
*/
// \callgraph
#pragma once
#include "ConcurrentFilteringAndSmoothing.h"
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/ISAM2.h>
#include <map>
#include <queue>
namespace gtsam {
/**
* A Levenberg-Marquardt Batch Filter that implements the Concurrent Filtering and Smoother interface.
*/
class ConcurrentBatchFilter : public ConcurrentFilter {
public:
typedef ConcurrentFilter Base; ///< typedef for base class
typedef std::map<Key, double> KeyTimestampMap; ///< Typedef for a Key-Timestamp map/database
typedef std::multimap<double, Key> TimestampKeyMap;///< Typedef for a Timestamp-Key map/database
/**
* Meta information returned about the update
*/
// TODO: Think of some more things to put here
struct Result {
size_t iterations; ///< The number of optimizer iterations performed
size_t nonlinearVariables; ///< The number of variables that can be relinearized
size_t linearVariables; ///< The number of variables that must keep a constant linearization point
double error; ///< The final factor graph error
Result() : iterations(0), nonlinearVariables(0), linearVariables(0), error(0) {};
};
/** Default constructor */
ConcurrentBatchFilter(const LevenbergMarquardtParams& parameters, double lag) : parameters_(parameters), lag_(lag) {};
/** Default destructor */
virtual ~ConcurrentBatchFilter() {};
// Implement a GTSAM standard 'print' function
void print(const std::string& s = "Concurrent Batch Filter:\n", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
/** Compute the current best estimate of all variables and return a full Values structure.
* If only a single variable is needed, it may be faster to call calculateEstimate(const KEY&).
*/
Values calculateEstimate() const {
return theta_;
}
/** Compute the current best estimate of a single variable. This is generally faster than
* calling the no-argument version of calculateEstimate if only specific variables are needed.
* @param key
* @return
*/
template<class VALUE>
VALUE calculateEstimate(const Key key) const {
return theta_.at<VALUE>(key);
}
/**
* Add new factors and variables to the filter.
*
* Add new measurements, and optionally new variables, to the filter.
* This runs a full update step of the derived filter algorithm
*
* @param newFactors The new factors to be added to the smoother
* @param newTheta Initialization points for new variables to be added to the filter
* You must include here all new variables occurring in newFactors that were not already
* in the filter.
*/
Result update(const NonlinearFactorGraph& newFactors = NonlinearFactorGraph(), const Values& newTheta = Values(),
const KeyTimestampMap& timestamps = KeyTimestampMap());
protected:
/** A typedef defining an Key-Factor mapping **/
typedef std::map<Key, std::set<Index> > FactorIndex;
LevenbergMarquardtParams parameters_; ///< LM parameters
double lag_; ///< Time before keys are transitioned from the filter to the smoother
NonlinearFactorGraph graph_; ///< The graph of all the smoother factors
Values theta_; ///< Current solution
TimestampKeyMap timestampKeyMap_; ///< The set of keys associated with each timestamp
KeyTimestampMap keyTimestampMap_; ///< The timestamp associated with each key
std::queue<size_t> availableSlots_; ///< The set of available factor graph slots caused by deleting factors
FactorIndex factorIndex_; ///< A cross-reference structure to allow efficient factor lookups by key
std::vector<size_t> smootherSummarizationSlots_; ///< The slots in graph for the last set of smoother summarized factors
Values separatorValues_;
// Storage for information to be sent to the smoother
NonlinearFactorGraph filterSummarization_; ///< A temporary holding place for calculated filter summarization factors to be sent to the smoother
Values rootValues_; ///< The set of keys to be kept in the root and their linearization points
NonlinearFactorGraph smootherFactors_; ///< A temporary holding place for the set of full nonlinear factors being sent to the smoother
Values smootherValues_; ///< A temporary holding place for the linearization points of all keys being sent to the smoother
/**
* Perform any required operations before the synchronization process starts.
* Called by 'synchronize'
*/
virtual void presync();
/**
* Populate the provided containers with factors that constitute the filter branch summarization
* needed by the smoother. Also, linearization points for the new root clique must be provided.
*
* @param summarizedFactors The summarized factors for the filter branch
* @param rootValues The linearization points of the root clique variables
*/
virtual void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors, Values& rootValues);
/**
* Populate the provided containers with factors being sent to the smoother from the filter. These
* may be original nonlinear factors, or factors encoding a summarization of the filter information.
* The specifics will be implementation-specific for a given filter.
*
* @param smootherFactors The new factors to be added to the smoother
* @param smootherValues The linearization points of any new variables
*/
virtual void getSmootherFactors(NonlinearFactorGraph& smootherFactors, Values& smootherValues);
/**
* Apply the updated version of the smoother branch summarized factors.
*
* @param summarizedFactors An updated version of the smoother branch summarized factors
*/
virtual void synchronize(const NonlinearFactorGraph& summarizedFactors);
/**
* Perform any required operations after the synchronization process finishes.
* Called by 'synchronize'
*/
virtual void postsync();
/** Augment the graph with a new factor
*
* @param factor The new factor to add to the graph
* @return The slot in the graph where the factor was inserted
*/
size_t insertFactor(const NonlinearFactor::shared_ptr& factor);
/** Remove a factor from the graph by slot index */
void removeFactor(size_t slot);
/** Remove the specified key from all data structures */
void removeKey(Key key);
/** Update the Timestamps associated with the keys */
void updateKeyTimestampMap(const KeyTimestampMap& newTimestamps);
/** Find the most recent timestamp of the system */
double getCurrentTimestamp() const;
/** Find all of the keys associated with timestamps before the provided time */
std::set<Key> findKeysBefore(double timestamp) const;
/** Find all of the keys associated with timestamps after the provided time */
std::set<Key> findKeysAfter(double timestamp) const;
/** Find all of the nonlinear factors that contain any of the provided keys */
std::set<size_t> findFactorsWithAny(const std::set<Key>& keys) const;
/** Find all of the nonlinear factors that contain only the provided keys */
std::set<size_t> findFactorsWithOnly(const std::set<Key>& keys) const;
/** Create linearized factors from any factors remaining after marginalizing out the requested keys */
NonlinearFactor::shared_ptr marginalizeKeysFromFactor(const NonlinearFactor::shared_ptr& factor, const std::set<Key>& remainingKeys) const;
private:
typedef BayesTree<GaussianConditional,ISAM2Clique>::sharedClique Clique;
static void SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent = "");
}; // ConcurrentBatchFilter
}/// namespace gtsam

View File

@ -0,0 +1,470 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentBatchSmoother.cpp
* @brief A Levenberg-Marquardt Batch Smoother that implements the
* Concurrent Filtering and Smoothing interface.
* @author Stephen Williams
*/
#include <gtsam_unstable/nonlinear/ConcurrentBatchSmoother.h>
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/inference/JunctionTree.h>
#include <gtsam/base/timing.h>
#include <boost/lambda/lambda.hpp>
namespace gtsam {
/* ************************************************************************* */
void ConcurrentBatchSmoother::SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent) {
std::cout << indent << "P( ";
BOOST_FOREACH(Index index, clique->conditional()->frontals()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
if(clique->conditional()->nrParents() > 0) {
std::cout << "| ";
}
BOOST_FOREACH(Index index, clique->conditional()->parents()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
BOOST_FOREACH(const Clique& child, clique->children()) {
SymbolicPrintTree(child, ordering, indent+" ");
}
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::print(const std::string& s,
const KeyFormatter& keyFormatter) const {
std::cout << s;
graph_.print("Factors:\n");
theta_.print("Values:\n");
}
/* ************************************************************************* */
ConcurrentBatchSmoother::Result ConcurrentBatchSmoother::update(const NonlinearFactorGraph& newFactors, const Values& newTheta) {
gttic(update);
// Create result structure
Result result;
gttic(augment_system);
// Add the new factors to the graph
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, newFactors) {
insertFactor(factor);
}
// Add the new variables to theta
theta_.insert(newTheta);
gttoc(augment_system);
// Optimize the graph, updating theta
gttic(optimize);
if(graph_.size() > 0){
// Create an L-M optimizer
Values linpoint;
linpoint.insert(theta_);
if(rootValues_.size() > 0) {
linpoint.insert(rootValues_);
}
LevenbergMarquardtOptimizer optimizer(graph_, linpoint, parameters_);
// Use a custom optimization loop so the linearization points can be controlled
double currentError;
do {
// Do next iteration
gttic(optimizer_iteration);
currentError = optimizer.error();
optimizer.iterate();
gttoc(optimizer_iteration);
// Force variables associated with root keys to keep the same linearization point
gttic(enforce_consistency);
if(rootValues_.size() > 0) {
// Put the old values of the root keys back into the optimizer state
optimizer.state().values.update(rootValues_);
optimizer.state().error = graph_.error(optimizer.state().values);
}
gttoc(enforce_consistency);
// Maybe show output
if(parameters_.verbosity >= NonlinearOptimizerParams::VALUES) optimizer.values().print("newValues");
if(parameters_.verbosity >= NonlinearOptimizerParams::ERROR) std::cout << "newError: " << optimizer.error() << std::endl;
} while(optimizer.iterations() < parameters_.maxIterations &&
!checkConvergence(parameters_.relativeErrorTol, parameters_.absoluteErrorTol,
parameters_.errorTol, currentError, optimizer.error(), parameters_.verbosity));
// Update theta from the optimizer, then remove root variables
theta_ = optimizer.values();
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) {
theta_.erase(key_value.key);
}
result.iterations = optimizer.state().iterations;
result.nonlinearVariables = theta_.size();
result.linearVariables = rootValues_.size();
result.error = optimizer.state().error;
}
gttoc(optimize);
// Move all of the Pre-Sync code to the end of the update. This allows the smoother to perform these
// calculations while the filter is still running
gttic(presync);
// Calculate and store the information passed up to the root clique. This requires:
// 1) Calculate an ordering that forces the rootKey variables to be in the root
// 2) Perform an elimination, constructing a Bayes Tree from the currnet
// variable values. This elimination will use the iSAM2 version of a clique so
// that cached factors are stored
// 3) Verify the root's cached factors involve only root keys; all others should
// be marginalized
// 4) Convert cached factors into 'Linearized' nonlinear factors
if(rootValues_.size() > 0) {
// Force variables associated with root keys to keep the same linearization point
gttic(enforce_consistency);
Values linpoint;
linpoint.insert(theta_);
linpoint.insert(rootValues_);
//linpoint.print("ConcurrentBatchSmoother::presync() LinPoint:\n");
gttoc(enforce_consistency);
// Calculate a root-constrained ordering
gttic(compute_ordering);
std::map<Key, int> constraints;
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) {
constraints[key_value.key] = 1;
}
Ordering ordering = *graph_.orderingCOLAMDConstrained(linpoint, constraints);
gttoc(compute_ordering);
// Create a Bayes Tree using iSAM2 cliques
gttic(create_bayes_tree);
JunctionTree<GaussianFactorGraph, ISAM2Clique> jt(*graph_.linearize(linpoint, ordering));
ISAM2Clique::shared_ptr root = jt.eliminate(parameters_.getEliminationFunction());
BayesTree<GaussianConditional, ISAM2Clique> bayesTree;
bayesTree.insert(root);
gttoc(create_bayes_tree);
//ordering.print("ConcurrentBatchSmoother::presync() Ordering:\n");
std::cout << "ConcurrentBatchSmoother::presync() Root Keys: "; BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) { std::cout << DefaultKeyFormatter(key_value.key) << " "; } std::cout << std::endl;
std::cout << "ConcurrentBatchSmoother::presync() Bayes Tree:" << std::endl;
SymbolicPrintTree(root, ordering, " ");
// Extract the marginal factors from the smoother
// For any non-filter factor that involves a root variable,
// calculate its marginal on the root variables using the
// current linearization point.
// Find all of the smoother branches as the children of root cliques that are not also root cliques
gttic(find_smoother_branches);
std::set<ISAM2Clique::shared_ptr> rootCliques;
std::set<ISAM2Clique::shared_ptr> smootherBranches;
BOOST_FOREACH(const Values::ConstKeyValuePair& key_value, rootValues_) {
const ISAM2Clique::shared_ptr& clique = bayesTree.nodes().at(ordering.at(key_value.key));
if(clique) {
rootCliques.insert(clique);
smootherBranches.insert(clique->children().begin(), clique->children().end());
}
}
BOOST_FOREACH(const ISAM2Clique::shared_ptr& rootClique, rootCliques) {
smootherBranches.erase(rootClique);
}
gttoc(find_smoother_branches);
// Extract the cached factors on the root cliques from the smoother branches
gttic(extract_cached_factors);
GaussianFactorGraph cachedFactors;
BOOST_FOREACH(const ISAM2Clique::shared_ptr& clique, smootherBranches) {
cachedFactors.push_back(clique->cachedFactor());
}
gttoc(extract_cached_factors);
std::cout << "ConcurrentBatchSmoother::presync() Cached Factors Before:" << std::endl;
BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, cachedFactors) {
std::cout << " g( ";
BOOST_FOREACH(Index index, factor->keys()) {
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
}
// Marginalize out any additional (non-root) variables
gttic(marginalize_extra_variables);
// The rootKeys have been ordered last, so their linear indices will be { linpoint.size()-rootKeys.size() :: linpoint.size()-1 }
Index minRootIndex = linpoint.size() - rootValues_.size();
// Calculate the set of keys to be marginalized
FastSet<Index> cachedIndices = cachedFactors.keys();
std::vector<Index> marginalizeIndices;
std::remove_copy_if(cachedIndices.begin(), cachedIndices.end(), std::back_inserter(marginalizeIndices), boost::lambda::_1 >= minRootIndex);
std::cout << "ConcurrentBatchSmoother::presync() Marginalize Keys: ";
BOOST_FOREACH(Index index, marginalizeIndices) { std::cout << DefaultKeyFormatter(ordering.key(index)) << " "; }
std::cout << std::endl;
// If non-root-keys are present, marginalize them out
if(marginalizeIndices.size() > 0) {
// Eliminate the extra variables, stored the remaining factors back into the 'cachedFactors' graph
GaussianConditional::shared_ptr conditional;
boost::tie(conditional, cachedFactors) = cachedFactors.eliminate(marginalizeIndices, parameters_.getEliminationFunction());
}
gttoc(marginalize_extra_variables);
std::cout << "ConcurrentBatchSmoother::presync() Cached Factors After:" << std::endl;
BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, cachedFactors) {
std::cout << " g( ";
BOOST_FOREACH(Index index, factor->keys()) {
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
}
// Convert factors into 'Linearized' nonlinear factors
gttic(store_cached_factors);
smootherSummarization_.resize(0);
BOOST_FOREACH(const GaussianFactor::shared_ptr& gaussianFactor, cachedFactors) {
LinearizedGaussianFactor::shared_ptr factor;
if(const JacobianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<JacobianFactor>(gaussianFactor))
factor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(rhs, ordering, linpoint));
else if(const HessianFactor::shared_ptr rhs = boost::dynamic_pointer_cast<HessianFactor>(gaussianFactor))
factor = LinearizedHessianFactor::shared_ptr(new LinearizedHessianFactor(rhs, ordering, linpoint));
else
throw std::invalid_argument("In ConcurrentBatchSmoother::presync(...), cached factor is neither a JacobianFactor nor a HessianFactor");
smootherSummarization_.push_back(factor);
}
gttoc(store_cached_factors);
std::cout << "ConcurrentBatchSmoother::presync() Smoother Summarization:" << std::endl;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, smootherSummarization_) {
std::cout << " f( ";
BOOST_FOREACH(Key key, factor->keys()) {
std::cout << DefaultKeyFormatter(key) << " ";
}
std::cout << ")" << std::endl;
}
}
gttoc(presync);
gttoc(update);
return result;
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::presync() {
gttic(presync);
gttoc(presync);
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::getSummarizedFactors(NonlinearFactorGraph& summarizedFactors) {
gttic(get_summarized_factors);
// Copy the previous calculated smoother summarization factors into the output
summarizedFactors.push_back(smootherSummarization_);
gttic(get_summarized_factors);
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::synchronize(const NonlinearFactorGraph& smootherFactors, const Values& smootherValues,
const NonlinearFactorGraph& summarizedFactors, const Values& rootValues) {
gttic(synchronize);
// Remove the previous filter summarization from the graph
BOOST_FOREACH(size_t slot, filterSummarizationSlots_) {
removeFactor(slot);
}
filterSummarizationSlots_.clear();
// Insert the new filter summarized factors
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, summarizedFactors) {
filterSummarizationSlots_.push_back(insertFactor(factor));
}
// Insert the new smoother factors
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, smootherFactors) {
insertFactor(factor);
}
// Insert new linpoints into the values
theta_.insert(smootherValues);
// Update the list of root keys
rootValues_ = rootValues;
gttoc(synchronize);
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::postsync() {
gttic(postsync);
gttoc(postsync);
}
/* ************************************************************************* */
size_t ConcurrentBatchSmoother::insertFactor(const NonlinearFactor::shared_ptr& factor) {
gttic(insert_factor);
// Insert the factor into an existing hole in the factor graph, if possible
size_t slot;
if(availableSlots_.size() > 0) {
slot = availableSlots_.front();
availableSlots_.pop();
graph_.replace(slot, factor);
} else {
slot = graph_.size();
graph_.push_back(factor);
}
// Update the FactorIndex
BOOST_FOREACH(Key key, *factor) {
factorIndex_[key].insert(slot);
}
gttoc(insert_factors);
return slot;
}
/* ************************************************************************* */
void ConcurrentBatchSmoother::removeFactor(size_t slot) {
gttic(remove_factors);
// Remove references to this factor from the FactorIndex
BOOST_FOREACH(Key key, *(graph_.at(slot))) {
factorIndex_[key].erase(slot);
}
// Remove this factor from the graph
graph_.remove(slot);
// Mark the factor slot as avaiable
availableSlots_.push(slot);
gttoc(remove_factors);
}
/* ************************************************************************* */
std::set<size_t> ConcurrentBatchSmoother::findFactorsWithAny(const std::set<Key>& keys) const {
// Find the set of factor slots for each specified key
std::set<size_t> factorSlots;
BOOST_FOREACH(Key key, keys) {
FactorIndex::const_iterator iter = factorIndex_.find(key);
if(iter != factorIndex_.end()) {
factorSlots.insert(iter->second.begin(), iter->second.end());
}
}
return factorSlots;
}
/* ************************************************************************* */
std::set<size_t> ConcurrentBatchSmoother::findFactorsWithOnly(const std::set<Key>& keys) const {
// Find the set of factor slots with any of the provided keys
std::set<size_t> factorSlots = findFactorsWithAny(keys);
// Test each factor for non-specified keys
std::set<size_t>::iterator slot = factorSlots.begin();
while(slot != factorSlots.end()) {
const NonlinearFactor::shared_ptr& factor = graph_.at(*slot);
std::set<Key> factorKeys(factor->begin(), factor->end()); // ensure the keys are sorted
if(!std::includes(keys.begin(), keys.end(), factorKeys.begin(), factorKeys.end())) {
factorSlots.erase(slot++);
} else {
++slot;
}
}
return factorSlots;
}
/* ************************************************************************* */
NonlinearFactor::shared_ptr ConcurrentBatchSmoother::marginalizeKeysFromFactor(const NonlinearFactor::shared_ptr& factor, const std::set<Key>& keysToKeep, const Values& theta) const {
factor->print("Factor Before:\n");
// Sort the keys for this factor
std::set<Key> factorKeys;
BOOST_FOREACH(Key key, *factor) {
factorKeys.insert(key);
}
// Calculate the set of keys to marginalize
std::set<Key> marginalizeKeys;
std::set_difference(factorKeys.begin(), factorKeys.end(), keysToKeep.begin(), keysToKeep.end(), std::inserter(marginalizeKeys, marginalizeKeys.end()));
std::set<Key> remainingKeys;
std::set_intersection(factorKeys.begin(), factorKeys.end(), keysToKeep.begin(), keysToKeep.end(), std::inserter(remainingKeys, remainingKeys.end()));
//
if(marginalizeKeys.size() == 0) {
// No keys need to be marginalized out. Simply return the original factor.
return factor;
} else if(marginalizeKeys.size() == factor->size()) {
// All keys need to be marginalized out. Return an empty factor
return NonlinearFactor::shared_ptr();
} else {
// (0) Create an ordering with the remaining keys last
Ordering ordering;
BOOST_FOREACH(Key key, marginalizeKeys) {
ordering.push_back(key);
}
BOOST_FOREACH(Key key, remainingKeys) {
ordering.push_back(key);
}
ordering.print("Ordering:\n");
// (1) construct a linear factor graph
GaussianFactorGraph graph;
graph.push_back( factor->linearize(theta, ordering) );
graph.at(0)->print("Linear Factor Before:\n");
// (2) solve for the marginal factor
// Perform partial elimination, resulting in a conditional probability ( P(MarginalizedVariable | RemainingVariables)
// and factors on the remaining variables ( f(RemainingVariables) ). These are the factors we need to add to iSAM2
std::vector<Index> variables;
BOOST_FOREACH(Key key, marginalizeKeys) {
variables.push_back(ordering.at(key));
}
// std::pair<GaussianFactorGraph::sharedConditional, GaussianFactorGraph> result = graph.eliminate(variables);
GaussianFactorGraph::EliminationResult result = EliminateQR(graph, marginalizeKeys.size());
result.first->print("Resulting Conditional:\n");
result.second->print("Resulting Linear Factor:\n");
// graph = result.second;
graph.replace(0, result.second);
// (3) convert the marginal factors into Linearized Factors
NonlinearFactor::shared_ptr marginalFactor;
assert(graph.size() <= 1);
if(graph.size() > 0) {
graph.at(0)->print("Linear Factor After:\n");
// These factors are all generated from BayesNet conditionals. They should all be Jacobians.
JacobianFactor::shared_ptr jacobianFactor = boost::dynamic_pointer_cast<JacobianFactor>(graph.at(0));
assert(jacobianFactor);
marginalFactor = LinearizedJacobianFactor::shared_ptr(new LinearizedJacobianFactor(jacobianFactor, ordering, theta));
}
marginalFactor->print("Factor After:\n");
return marginalFactor;
}
}
/* ************************************************************************* */
}/// namespace gtsam

View File

@ -0,0 +1,164 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentBatchSmoother.h
* @brief A Levenberg-Marquardt Batch Smoother that implements the
* Concurrent Filtering and Smoothing interface.
* @author Stephen Williams
*/
// \callgraph
#pragma once
#include "ConcurrentFilteringAndSmoothing.h"
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/ISAM2.h>
#include <queue>
namespace gtsam {
/**
* A Levenberg-Marquardt Batch Smoother that implements the Concurrent Filtering and Smoother interface.
*/
class ConcurrentBatchSmoother : public ConcurrentSmoother {
public:
typedef ConcurrentSmoother Base; ///< typedef for base class
/**
* Meta information returned about the update
*/
// TODO: Think of some more things to put here
struct Result {
size_t iterations; ///< The number of optimizer iterations performed
size_t nonlinearVariables; ///< The number of variables that can be relinearized
size_t linearVariables; ///< The number of variables that must keep a constant linearization point
double error; ///< The final factor graph error
Result() : iterations(0), nonlinearVariables(0), linearVariables(0), error(0) {};
};
/** Default constructor */
ConcurrentBatchSmoother(const LevenbergMarquardtParams& parameters) : parameters_(parameters) {};
/** Default destructor */
virtual ~ConcurrentBatchSmoother() {};
// Implement a GTSAM standard 'print' function
void print(const std::string& s = "Concurrent Batch Smoother:\n", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
/** Compute the current best estimate of all variables and return a full Values structure.
* If only a single variable is needed, it may be faster to call calculateEstimate(const KEY&).
*/
Values calculateEstimate() const {
return theta_;
}
/** Compute the current best estimate of a single variable. This is generally faster than
* calling the no-argument version of calculateEstimate if only specific variables are needed.
* @param key
* @return
*/
template<class VALUE>
VALUE calculateEstimate(const Key key) const {
return theta_.at<VALUE>(key);
}
/**
* Add new factors and variables to the smoother.
*
* Add new measurements, and optionally new variables, to the smoother.
* This runs a full step of the ISAM2 algorithm, relinearizing and updating
* the solution as needed, according to the wildfire and relinearize
* thresholds.
*
* @param newFactors The new factors to be added to the smoother
* @param newTheta Initialization points for new variables to be added to the smoother
* You must include here all new variables occuring in newFactors (which were not already
* in the smoother). There must not be any variables here that do not occur in newFactors,
* and additionally, variables that were already in the system must not be included here.
*/
Result update(const NonlinearFactorGraph& newFactors = NonlinearFactorGraph(), const Values& newTheta = Values());
protected:
/** A typedef defining an Key-Factor mapping **/
typedef std::map<Key, std::set<Index> > FactorIndex;
LevenbergMarquardtParams parameters_; ///< LM parameters
NonlinearFactorGraph graph_; ///< The graph of all the smoother factors
Values theta_; ///< Current solution
Values rootValues_; ///< The set of keys to be kept in the root and their linearization points
std::queue<size_t> availableSlots_; ///< The set of available factor graph slots caused by deleting factors
FactorIndex factorIndex_; ///< A cross-reference structure to allow efficient factor lookups by key
std::vector<size_t> filterSummarizationSlots_; ///< The slots in graph for the last set of filter summarized factors
NonlinearFactorGraph smootherSummarization_; ///< A temporary holding place for calculated smoother summarization
/**
* Perform any required operations before the synchronization process starts.
* Called by 'synchronize'
*/
virtual void presync();
/**
* Populate the provided containers with factors that constitute the smoother branch summarization
* needed by the filter.
*
* @param summarizedFactors The summarized factors for the filter branch
*/
virtual void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors);
/**
* Apply the new smoother factors sent by the filter, and the updated version of the filter
* branch summarized factors.
*
* @param smootherFactors A set of new factors added to the smoother from the filter
* @param smootherValues Linearization points for any new variables
* @param summarizedFactors An updated version of the filter branch summarized factors
* @param rootValues The linearization point of the root variables
*/
virtual void synchronize(const NonlinearFactorGraph& smootherFactors, const Values& smootherValues,
const NonlinearFactorGraph& summarizedFactors, const Values& rootValues);
/**
* Perform any required operations after the synchronization process finishes.
* Called by 'synchronize'
*/
virtual void postsync();
/** Augment the graph with a new factor
*
* @param factors The factor to add to the graph
* @return The slot in the graph it was inserted into
*/
size_t insertFactor(const NonlinearFactor::shared_ptr& factor);
/** Remove a factor from the graph by slot index */
void removeFactor(size_t slot);
/** Find all of the nonlinear factors that contain any of the provided keys */
std::set<size_t> findFactorsWithAny(const std::set<Key>& keys) const;
/** Find all of the nonlinear factors that contain only the provided keys */
std::set<size_t> findFactorsWithOnly(const std::set<Key>& keys) const;
/** Create a linearized factor from the information remaining after marginalizing out the requested keys */
NonlinearFactor::shared_ptr marginalizeKeysFromFactor(const NonlinearFactor::shared_ptr& factor, const std::set<Key>& keysToKeep, const Values& theta) const;
private:
typedef BayesTree<GaussianConditional,ISAM2Clique>::sharedClique Clique;
static void SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent = "");
}; // ConcurrentBatchSmoother
}/// namespace gtsam

View File

@ -0,0 +1,49 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentFilteringAndSmoothing.cpp
* @brief Base classes for the 'filter' and 'smoother' portion of the Concurrent
* Filtering and Smoothing architecture, as well as an external synchronization
* function. These classes act as an interface only.
* @author Stephen Williams
*/
// \callgraph
#include "ConcurrentFilteringAndSmoothing.h"
namespace gtsam {
void synchronize(ConcurrentFilter& filter, ConcurrentSmoother& smoother) {
NonlinearFactorGraph smootherFactors, filterSumarization, smootherSummarization;
Values smootherValues, rootValues;
// Call the pre-sync functions of the filter and smoother
filter.presync();
smoother.presync();
// Get the updates from the smoother and apply them to the filter
smoother.getSummarizedFactors(smootherSummarization);
filter.synchronize(smootherSummarization);
// Get the updates from the filter and apply them to the smoother
filter.getSmootherFactors(smootherFactors, smootherValues);
filter.getSummarizedFactors(filterSumarization, rootValues);
smoother.synchronize(smootherFactors, smootherValues, filterSumarization, rootValues);
// Call the post-sync functions of the filter and smoother
filter.postsync();
smoother.postsync();
}
}/// namespace gtsam

View File

@ -0,0 +1,137 @@
/* ----------------------------------------------------------------------------
* 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 ConcurrentFilteringAndSmoothing.h
* @brief Base classes for the 'filter' and 'smoother' portion of the Concurrent
* Filtering and Smoothing architecture, as well as an external synchronization
* function. These classes act as an interface only.
* @author Stephen Williams
*/
// \callgraph
#pragma once
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
namespace gtsam {
// Forward declare the Filter and Smoother classes for the 'synchronize' function
class ConcurrentFilter;
class ConcurrentSmoother;
void synchronize(ConcurrentFilter& filter, ConcurrentSmoother& smoother);
/**
* The interface for the 'Filter' portion of the Concurrent Filtering and Smoother architecture.
*/
class ConcurrentFilter {
protected:
friend void synchronize(ConcurrentFilter& filter, ConcurrentSmoother& smoother);
/** Default constructor */
ConcurrentFilter() {};
/** Default destructor */
virtual ~ConcurrentFilter() {};
/**
* Perform any required operations before the synchronization process starts.
* Called by 'synchronize'
*/
virtual void presync() {};
/**
* Populate the provided containers with factors that constitute the filter branch summarization
* needed by the smoother. Also, linearization points for the new root clique must be provided.
*
* @param summarizedFactors The summarized factors for the filter branch
* @param rootValues The linearization points of the root clique variables
*/
virtual void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors, Values& rootValues) = 0;
/**
* Populate the provided containers with factors being sent to the smoother from the filter. These
* may be original nonlinear factors, or factors encoding a summarization of the filter information.
* The specifics will be implementation-specific for a given filter.
*
* @param smootherFactors The new factors to be added to the smoother
* @param smootherValues The linearization points of any new variables
*/
virtual void getSmootherFactors(NonlinearFactorGraph& smootherFactors, Values& smootherValues) = 0;
/**
* Apply the updated version of the smoother branch summarized factors.
*
* @param summarizedFactors An updated version of the smoother branch summarized factors
*/
virtual void synchronize(const NonlinearFactorGraph& summarizedFactors) = 0;
/**
* Perform any required operations after the synchronization process finishes.
* Called by 'synchronize'
*/
virtual void postsync() {};
}; // ConcurrentFilter
/**
* The interface for the 'Smoother' portion of the Concurrent Filtering and Smoother architecture.
*/
class ConcurrentSmoother {
protected:
friend void synchronize(ConcurrentFilter& filter, ConcurrentSmoother& smoother);
/** Default constructor */
ConcurrentSmoother() {};
/** Default destructor */
virtual ~ConcurrentSmoother() {};
/**
* Perform any required operations before the synchronization process starts.
* Called by 'synchronize'
*/
virtual void presync() {};
/**
* Populate the provided containers with factors that constitute the smoother branch summarization
* needed by the filter.
*
* @param summarizedFactors The summarized factors for the filter branch
*/
virtual void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors) = 0;
/**
* Apply the new smoother factors sent by the filter, and the updated version of the filter
* branch summarized factors.
*
* @param smootherFactors A set of new factors added to the smoother from the filter
* @param smootherValues Linearization points for any new variables
* @param summarizedFactors An updated version of the filter branch summarized factors
* @param rootValues The linearization point of the root variables
*/
virtual void synchronize(const NonlinearFactorGraph& smootherFactors, const Values& smootherValues,
const NonlinearFactorGraph& summarizedFactors, const Values& rootValues) = 0;
/**
* Perform any required operations after the synchronization process finishes.
* Called by 'synchronize'
*/
virtual void postsync() {};
}; // ConcurrentSmoother
}/// namespace gtsam

View File

@ -0,0 +1,281 @@
/* ----------------------------------------------------------------------------
* 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 LinearizedFactor.cpp
* @brief A dummy factor that allows a linear factor to act as a nonlinear factor
* @author Alex Cunningham
*/
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <boost/foreach.hpp>
#include <iostream>
namespace gtsam {
/* ************************************************************************* */
LinearizedGaussianFactor::LinearizedGaussianFactor(const GaussianFactor::shared_ptr& gaussian, const Ordering& ordering, const Values& lin_points) {
// Extract the keys and linearization points
BOOST_FOREACH(const Index& idx, gaussian->keys()) {
// find full symbol
if (idx < ordering.size()) {
Key key = ordering.key(idx);
// extract linearization point
assert(lin_points.exists(key));
this->lin_points_.insert(key, lin_points.at(key));
// store keys
this->keys_.push_back(key);
} else {
throw std::runtime_error("LinearizedGaussianFactor: could not find index in decoder!");
}
}
}
/* ************************************************************************* */
LinearizedJacobianFactor::LinearizedJacobianFactor() : Ab_(matrix_) {
}
/* ************************************************************************* */
LinearizedJacobianFactor::LinearizedJacobianFactor(const JacobianFactor::shared_ptr& jacobian,
const Ordering& ordering, const Values& lin_points)
: Base(jacobian, ordering, lin_points), Ab_(matrix_) {
// Get the Ab matrix from the Jacobian factor, with any covariance baked in
AbMatrix fullMatrix = jacobian->matrix_augmented(true);
// Create the dims array
size_t dims[jacobian->size() + 1];
size_t index = 0;
for(JacobianFactor::const_iterator iter = jacobian->begin(); iter != jacobian->end(); ++iter) {
dims[index++] = jacobian->getDim(iter);
}
dims[index] = 1;
// Update the BlockInfo accessor
BlockAb Ab(fullMatrix, dims, dims+jacobian->size()+1);
Ab.swap(Ab_);
}
/* ************************************************************************* */
void LinearizedJacobianFactor::print(const std::string& s, const KeyFormatter& keyFormatter) const {
std::cout << s << std::endl;
std::cout << "Nonlinear Keys: ";
BOOST_FOREACH(const Key& key, this->keys())
std::cout << keyFormatter(key) << " ";
std::cout << std::endl;
for(const_iterator key=begin(); key!=end(); ++key)
std::cout << boost::format("A[%1%]=\n")%keyFormatter(*key) << A(*key) << std::endl;
std::cout << "b=\n" << b() << std::endl;
lin_points_.print("Linearization Point: ");
}
/* ************************************************************************* */
bool LinearizedJacobianFactor::equals(const NonlinearFactor& expected, double tol) const {
const This *e = dynamic_cast<const This*> (&expected);
if (e) {
Matrix thisMatrix = this->Ab_.range(0, Ab_.nBlocks());
Matrix rhsMatrix = e->Ab_.range(0, Ab_.nBlocks());
return Base::equals(expected, tol)
&& lin_points_.equals(e->lin_points_, tol)
&& equal_with_abs_tol(thisMatrix, rhsMatrix, tol);
} else {
return false;
}
}
/* ************************************************************************* */
double LinearizedJacobianFactor::error(const Values& c) const {
Vector errorVector = error_vector(c);
return 0.5 * errorVector.dot(errorVector);
}
/* ************************************************************************* */
boost::shared_ptr<GaussianFactor>
LinearizedJacobianFactor::linearize(const Values& c, const Ordering& ordering) const {
// Create the 'terms' data structure for the Jacobian constructor
std::vector<std::pair<Index, Matrix> > terms;
BOOST_FOREACH(Key key, keys()) {
terms.push_back(std::make_pair(ordering[key], this->A(key)));
}
// compute rhs
Vector b = -error_vector(c);
return boost::shared_ptr<GaussianFactor>(new JacobianFactor(terms, b, noiseModel::Unit::Create(dim())));
}
/* ************************************************************************* */
Vector LinearizedJacobianFactor::error_vector(const Values& c) const {
Vector errorVector = -b();
BOOST_FOREACH(Key key, this->keys()) {
const Value& newPt = c.at(key);
const Value& linPt = lin_points_.at(key);
Vector d = linPt.localCoordinates_(newPt);
const constABlock A = this->A(key);
errorVector += A * d;
}
return errorVector;
}
/* ************************************************************************* */
LinearizedHessianFactor::LinearizedHessianFactor() : info_(matrix_) {
}
/* ************************************************************************* */
LinearizedHessianFactor::LinearizedHessianFactor(const HessianFactor::shared_ptr& hessian,
const Ordering& ordering, const Values& lin_points)
: Base(hessian, ordering, lin_points), info_(matrix_) {
// Copy the augmented matrix holding G, g, and f
Matrix fullMatrix = hessian->info();
// Create the dims array
size_t dims[hessian->size() + 1];
size_t index = 0;
for(HessianFactor::const_iterator iter = hessian->begin(); iter != hessian->end(); ++iter) {
dims[index++] = hessian->getDim(iter);
}
dims[index] = 1;
// Update the BlockInfo accessor
BlockInfo infoMatrix(fullMatrix, dims, dims+hessian->size()+1);
infoMatrix.swap(info_);
}
/* ************************************************************************* */
void LinearizedHessianFactor::print(const std::string& s, const KeyFormatter& keyFormatter) const {
std::cout << s << std::endl;
std::cout << "Nonlinear Keys: ";
BOOST_FOREACH(const Key& key, this->keys())
std::cout << keyFormatter(key) << " ";
std::cout << std::endl;
gtsam::print(Matrix(info_.range(0,info_.nBlocks(), 0,info_.nBlocks()).selfadjointView<Eigen::Upper>()), "Ab^T * Ab: ");
lin_points_.print("Linearization Point: ");
}
/* ************************************************************************* */
bool LinearizedHessianFactor::equals(const NonlinearFactor& expected, double tol) const {
const This *e = dynamic_cast<const This*> (&expected);
if (e) {
Matrix thisMatrix = this->info_.full().selfadjointView<Eigen::Upper>();
thisMatrix(thisMatrix.rows()-1, thisMatrix.cols()-1) = 0.0;
Matrix rhsMatrix = e->info_.full().selfadjointView<Eigen::Upper>();
rhsMatrix(rhsMatrix.rows()-1, rhsMatrix.cols()-1) = 0.0;
return Base::equals(expected, tol)
&& lin_points_.equals(e->lin_points_, tol)
&& equal_with_abs_tol(thisMatrix, rhsMatrix, tol);
} else {
return false;
}
}
/* ************************************************************************* */
double LinearizedHessianFactor::error(const Values& c) const {
// Construct an error vector in key-order from the Values
Vector dx = zero(dim());
size_t index = 0;
for(unsigned int i = 0; i < this->size(); ++i){
Key key = this->keys()[i];
const Value& newPt = c.at(key);
const Value& linPt = lin_points_.at(key);
dx.segment(index, linPt.dim()) = linPt.localCoordinates_(newPt);
index += linPt.dim();
}
// error 0.5*(f - 2*x'*g + x'*G*x)
double f = constantTerm();
double xtg = dx.dot(linearTerm());
double xGx = dx.transpose() * squaredTerm().selfadjointView<Eigen::Upper>() * dx;
return 0.5 * (f - 2.0 * xtg + xGx);
}
/* ************************************************************************* */
boost::shared_ptr<GaussianFactor>
LinearizedHessianFactor::linearize(const Values& c, const Ordering& ordering) const {
// Use the ordering to convert the keys into indices;
std::vector<Index> js;
BOOST_FOREACH(Key key, this->keys()){
js.push_back(ordering.at(key));
}
// Make a copy of the info matrix
Matrix newMatrix;
SymmetricBlockView<Matrix> newInfo(newMatrix);
newInfo.assignNoalias(info_);
// Construct an error vector in key-order from the Values
Vector dx = zero(dim());
size_t index = 0;
for(unsigned int i = 0; i < this->size(); ++i){
Key key = this->keys()[i];
const Value& newPt = c.at(key);
const Value& linPt = lin_points_.at(key);
dx.segment(index, linPt.dim()) = linPt.localCoordinates_(newPt);
index += linPt.dim();
}
// f2 = f1 - 2*dx'*g1 + dx'*G1*dx
//newInfo(this->size(), this->size())(0,0) += -2*dx.dot(linearTerm()) + dx.transpose() * squaredTerm().selfadjointView<Eigen::Upper>() * dx;
double f = constantTerm() - 2*dx.dot(linearTerm()) + dx.transpose() * squaredTerm().selfadjointView<Eigen::Upper>() * dx;
// g2 = g1 - G1*dx
//newInfo.rangeColumn(0, this->size(), this->size(), 0) -= squaredTerm().selfadjointView<Eigen::Upper>() * dx;
Vector g = linearTerm() - squaredTerm().selfadjointView<Eigen::Upper>() * dx;
std::vector<Vector> gs;
for(size_t i = 0; i < info_.nBlocks()-1; ++i) {
gs.push_back(g.segment(info_.offset(i), info_.offset(i+1) - info_.offset(i)));
}
// G2 = G1
// Do Nothing
std::vector<Matrix> Gs;
for(size_t i = 0; i < info_.nBlocks()-1; ++i) {
for(size_t j = i; j < info_.nBlocks()-1; ++j) {
Gs.push_back(info_(i,j));
}
}
// Create a Hessian Factor from the modified info matrix
//return boost::shared_ptr<GaussianFactor>(new HessianFactor(js, newInfo));
return boost::shared_ptr<GaussianFactor>(new HessianFactor(js, Gs, gs, f));
}
} // \namespace aspn

View File

@ -0,0 +1,289 @@
/* ----------------------------------------------------------------------------
* 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 LinearizedFactor.h
* @brief A dummy factor that allows a linear factor to act as a nonlinear factor
* @author Alex Cunningham
*/
#pragma once
#include <vector>
#include <gtsam/nonlinear/Ordering.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
#include <gtsam/linear/JacobianFactor.h>
#include <gtsam/linear/HessianFactor.h>
#include <gtsam/base/blockMatrices.h>
namespace gtsam {
/**
* A base factor class for the Jacobian and Hessian linearized factors
*/
class LinearizedGaussianFactor : public NonlinearFactor {
public:
/** base type */
typedef NonlinearFactor Base;
typedef LinearizedGaussianFactor This;
/** shared pointer for convenience */
typedef boost::shared_ptr<LinearizedGaussianFactor> shared_ptr;
protected:
/** linearization points for error calculation */
Values lin_points_;
public:
/** default constructor for serialization */
LinearizedGaussianFactor() {};
/**
* @param gaussian: A jacobian or hessian factor
* @param ordering: The full ordering used to linearize this factor
* @param lin_points: The linearization points for, at least, the variables used by this factor
*/
LinearizedGaussianFactor(const GaussianFactor::shared_ptr& gaussian, const Ordering& ordering, const Values& lin_points);
virtual ~LinearizedGaussianFactor() {};
// access functions
const Values& linearizationPoint() const { return lin_points_; }
private:
/** Serialization function */
friend class boost::serialization::access;
template<class ARCHIVE>
void serialize(ARCHIVE & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("LinearizedGaussianFactor",
boost::serialization::base_object<Base>(*this));
ar & BOOST_SERIALIZATION_NVP(lin_points_);
}
};
/**
* A factor that takes a linear, Jacobian factor and inserts it into
* a nonlinear graph.
*/
class LinearizedJacobianFactor : public LinearizedGaussianFactor {
public:
/** base type */
typedef LinearizedGaussianFactor Base;
typedef LinearizedJacobianFactor This;
/** shared pointer for convenience */
typedef boost::shared_ptr<LinearizedJacobianFactor> shared_ptr;
typedef Matrix AbMatrix;
typedef VerticalBlockView<AbMatrix> BlockAb;
typedef BlockAb::Block ABlock;
typedef BlockAb::constBlock constABlock;
typedef BlockAb::Column BVector;
typedef BlockAb::constColumn constBVector;
protected:
// // store components of a jacobian factor
// typedef std::map<Key, Matrix> KeyMatrixMap;
// KeyMatrixMap matrices_;
// Vector b_;
AbMatrix matrix_; // the full matrix corresponding to the factor
BlockAb Ab_; // the block view of the full matrix
public:
/** default constructor for serialization */
LinearizedJacobianFactor();
/**
* @param jacobian: A jacobian factor
* @param ordering: The ordering used to linearize this factor
* @param lin_points: The linearization points for, at least, the variables used by this factor
*/
LinearizedJacobianFactor(const JacobianFactor::shared_ptr& jacobian,
const Ordering& ordering, const Values& lin_points);
virtual ~LinearizedJacobianFactor() {}
/// @return a deep copy of this factor
virtual gtsam::NonlinearFactor::shared_ptr clone() const {
return boost::static_pointer_cast<gtsam::NonlinearFactor>(
gtsam::NonlinearFactor::shared_ptr(new This(*this))); }
// Testable
/** print function */
virtual void print(const std::string& s="", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
/** equals function with optional tolerance */
virtual bool equals(const NonlinearFactor& expected, double tol = 1e-9) const;
// access functions
const constBVector b() const { return Ab_.column(size(), 0); }
const constABlock A() const { return Ab_.range(0, size()); };
const constABlock A(Key key) const { return Ab_(std::find(begin(), end(), key) - begin()); }
/** get the dimension of the factor (number of rows on linearization) */
size_t dim() const { return Ab_.rows(); };
/** Calculate the error of the factor */
double error(const Values& c) const;
/**
* linearize to a GaussianFactor
* Reimplemented from NoiseModelFactor to directly copy out the
* matrices and only update the RHS b with an updated residual
*/
boost::shared_ptr<GaussianFactor> linearize(
const Values& c, const Ordering& ordering) const;
/** (A*x-b) */
Vector error_vector(const Values& c) const;
private:
/** Serialization function */
friend class boost::serialization::access;
template<class ARCHIVE>
void serialize(ARCHIVE & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("LinearizedJacobianFactor",
boost::serialization::base_object<Base>(*this));
ar & BOOST_SERIALIZATION_NVP(matrix_);
ar & BOOST_SERIALIZATION_NVP(Ab_);
}
};
/**
* A factor that takes a linear, Hessian factor and inserts it into
* a nonlinear graph.
*/
class LinearizedHessianFactor : public LinearizedGaussianFactor {
public:
/** base type */
typedef LinearizedGaussianFactor Base;
typedef LinearizedHessianFactor This;
/** shared pointer for convenience */
typedef boost::shared_ptr<LinearizedHessianFactor> shared_ptr;
/** hessian block data types */
typedef Matrix InfoMatrix; ///< The full augmented Hessian
typedef SymmetricBlockView<InfoMatrix> BlockInfo; ///< A blockwise view of the Hessian
typedef BlockInfo::Block Block; ///< A block from the Hessian matrix
typedef BlockInfo::constBlock constBlock; ///< A block from the Hessian matrix (const version)
typedef BlockInfo::Column Column; ///< A column containing the linear term h
typedef BlockInfo::constColumn constColumn; ///< A column containing the linear term h (const version)
protected:
InfoMatrix matrix_; ///< The full augmented information matrix, s.t. the quadratic error is 0.5*[x -1]'*H*[x -1]
BlockInfo info_; ///< The block view of the full information matrix.
public:
/** default constructor for serialization */
LinearizedHessianFactor();
/**
* Use this constructor with the ordering used to linearize the graph
* @param hessian: A hessian factor
* @param ordering: The ordering used to linearize this factor
* @param lin_points: The linearization points for, at least, the variables used by this factor
*/
LinearizedHessianFactor(const HessianFactor::shared_ptr& hessian,
const Ordering& ordering, const Values& lin_points);
virtual ~LinearizedHessianFactor() {}
/// @return a deep copy of this factor
virtual gtsam::NonlinearFactor::shared_ptr clone() const {
return boost::static_pointer_cast<gtsam::NonlinearFactor>(
gtsam::NonlinearFactor::shared_ptr(new This(*this))); }
// Testable
/** print function */
virtual void print(const std::string& s="", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const;
/** equals function with optional tolerance */
virtual bool equals(const NonlinearFactor& expected, double tol = 1e-9) const;
/** Return the constant term \f$ f \f$ as described above
* @return The constant term \f$ f \f$
*/
double constantTerm() const { return info_(this->size(), this->size())(0,0); };
/** Return the part of linear term \f$ g \f$ as described above corresponding to the requested variable.
* @param j Which block row to get, as an iterator pointing to the slot in this factor. You can
* use, for example, begin() + 2 to get the 3rd variable in this factor.
* @return The linear term \f$ g \f$ */
constColumn linearTerm(const_iterator j) const { return info_.column(j - this->begin(), this->size(), 0); }
/** Return the complete linear term \f$ g \f$ as described above.
* @return The linear term \f$ g \f$ */
constColumn linearTerm() const { return info_.rangeColumn(0, this->size(), this->size(), 0); };
/** Return a view of the block at (j1,j2) of the <emph>upper-triangular part</emph> of the
* squared term \f$ H \f$, no data is copied. See HessianFactor class documentation
* above to explain that only the upper-triangular part of the information matrix is stored
* and returned by this function.
* @param j1 Which block row to get, as an iterator pointing to the slot in this factor. You can
* use, for example, begin() + 2 to get the 3rd variable in this factor.
* @param j2 Which block column to get, as an iterator pointing to the slot in this factor. You can
* use, for example, begin() + 2 to get the 3rd variable in this factor.
* @return A view of the requested block, not a copy.
*/
constBlock squaredTerm(const_iterator j1, const_iterator j2) const { return info_(j1-begin(), j2-begin()); }
/** Return the <emph>upper-triangular part</emph> of the full squared term, as described above.
* See HessianFactor class documentation above to explain that only the
* upper-triangular part of the information matrix is stored and returned by this function.
*/
constBlock squaredTerm() const { return info_.range(0, this->size(), 0, this->size()); }
/** get the dimension of the factor (number of rows on linearization) */
size_t dim() const { return matrix_.rows() - 1; };
/** Calculate the error of the factor */
double error(const Values& c) const;
/**
* linearize to a GaussianFactor
* Reimplemented from NoiseModelFactor to directly copy out the
* matrices and only update the RHS b with an updated residual
*/
boost::shared_ptr<GaussianFactor> linearize(
const Values& c, const Ordering& ordering) const;
private:
/** Serialization function */
friend class boost::serialization::access;
template<class ARCHIVE>
void serialize(ARCHIVE & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("LinearizedHessianFactor",
boost::serialization::base_object<Base>(*this));
ar & BOOST_SERIALIZATION_NVP(matrix_);
ar & BOOST_SERIALIZATION_NVP(info_);
}
};
} // \namespace aspn

View File

@ -0,0 +1,181 @@
/* ----------------------------------------------------------------------------
* 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 testIncrementalFixedLagSmoother.cpp
* @brief Unit tests for the Incremental Fixed-Lag Smoother
* @author Stephen Williams (swilliams8@gatech.edu)
* @date May 23, 2012
*/
#include <gtsam_unstable/nonlinear/BatchFixedLagSmoother.h>
#include <CppUnitLite/TestHarness.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/nonlinear/Ordering.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/nonlinear/Key.h>
#include <gtsam/linear/GaussianBayesNet.h>
#include <gtsam/linear/GaussianSequentialSolver.h>
#include <gtsam/geometry/Point2.h>
#include <gtsam/base/debug.h>
using namespace std;
using namespace gtsam;
/* ************************************************************************* */
bool check_smoother(const NonlinearFactorGraph& fullgraph, const Values& fullinit, const BatchFixedLagSmoother& smoother, const Key& key) {
Ordering ordering = *fullgraph.orderingCOLAMD(fullinit);
GaussianFactorGraph linearized = *fullgraph.linearize(fullinit, ordering);
GaussianBayesNet gbn = *GaussianSequentialSolver(linearized).eliminate();
VectorValues delta = optimize(gbn);
Values fullfinal = fullinit.retract(delta, ordering);
Point2 expected = fullfinal.at<Point2>(key);
Point2 actual = smoother.calculateEstimate<Point2>(key);
return assert_equal(expected, actual);
}
/* ************************************************************************* */
TEST_UNSAFE( BatchFixedLagSmoother, Example )
{
// Test the BatchFixedLagSmoother in a pure linear environment. Thus, full optimization and
// the BatchFixedLagSmoother should be identical (even with the linearized approximations at
// the end of the smoothing lag)
SETDEBUG("BatchFixedLagSmoother update", false);
// Set up parameters
SharedDiagonal odometerNoise = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1));
SharedDiagonal loopNoise = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1));
// Create a Fixed-Lag Smoother
typedef BatchFixedLagSmoother::KeyTimestampMap Timestamps;
BatchFixedLagSmoother smoother(LevenbergMarquardtParams(), 7.0);
// Create containers to keep the full graph
Values fullinit;
NonlinearFactorGraph fullgraph;
// i keeps track of the time step
size_t i = 0;
// Add a prior at time 0 and update the HMF
{
Key key0(0);
NonlinearFactorGraph newFactors;
Values newValues;
Timestamps newTimestamps;
newFactors.add(PriorFactor<Point2>(key0, Point2(0.0, 0.0), odometerNoise));
newValues.insert(key0, Point2(0.01, 0.01));
newTimestamps[key0] = 0.0;
fullgraph.push_back(newFactors);
fullinit.insert(newValues);
// Update the smoother
smoother.update(newFactors, newValues, newTimestamps);
// Check
CHECK(check_smoother(fullgraph, fullinit, smoother, key0));
++i;
}
// Add odometry from time 0 to time 5
while(i <= 5) {
Key key1(i-1);
Key key2(i);
NonlinearFactorGraph newFactors;
Values newValues;
Timestamps newTimestamps;
newFactors.add(BetweenFactor<Point2>(key1, key2, Point2(1.0, 0.0), odometerNoise));
newValues.insert(key2, Point2(double(i)+0.1, -0.1));
newTimestamps[key2] = double(i);
fullgraph.push_back(newFactors);
fullinit.insert(newValues);
// Update the smoother
smoother.update(newFactors, newValues, newTimestamps);
// Check
CHECK(check_smoother(fullgraph, fullinit, smoother, key2));
++i;
}
// Add odometry from time 5 to 6 to the HMF and a loop closure at time 5 to the TSM
{
// Add the odometry factor to the HMF
Key key1(i-1);
Key key2(i);
NonlinearFactorGraph newFactors;
Values newValues;
Timestamps newTimestamps;
newFactors.add(BetweenFactor<Point2>(key1, key2, Point2(1.0, 0.0), odometerNoise));
newFactors.add(BetweenFactor<Point2>(Key(2), Key(5), Point2(3.5, 0.0), loopNoise));
newValues.insert(key2, Point2(double(i)+0.1, -0.1));
newTimestamps[key2] = double(i);
fullgraph.push_back(newFactors);
fullinit.insert(newValues);
// Update the smoother
smoother.update(newFactors, newValues, newTimestamps);
// Check
CHECK(check_smoother(fullgraph, fullinit, smoother, key2));
++i;
}
// Add odometry from time 6 to time 15
while(i <= 15) {
Key key1(i-1);
Key key2(i);
NonlinearFactorGraph newFactors;
Values newValues;
Timestamps newTimestamps;
newFactors.add(BetweenFactor<Point2>(key1, key2, Point2(1.0, 0.0), odometerNoise));
newValues.insert(key2, Point2(double(i)+0.1, -0.1));
newTimestamps[key2] = double(i);
fullgraph.push_back(newFactors);
fullinit.insert(newValues);
// Update the smoother
smoother.update(newFactors, newValues, newTimestamps);
// Check
CHECK(check_smoother(fullgraph, fullinit, smoother, key2));
++i;
}
}
/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr);}
/* ************************************************************************* */

View File

@ -0,0 +1,783 @@
/* ----------------------------------------------------------------------------
* 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 testConcurrentBatchFilter.cpp
* @brief Unit tests for the Concurrent Batch Filter
* @author Stephen Williams (swilliams8@gatech.edu)
* @date Jan 5, 2013
*/
#include <gtsam_unstable/nonlinear/ConcurrentBatchFilter.h>
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/nonlinear/ISAM2.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Ordering.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/nonlinear/Symbol.h>
#include <gtsam/nonlinear/Key.h>
#include <gtsam/inference/JunctionTree.h>
#include <gtsam/geometry/Pose3.h>
#include <CppUnitLite/TestHarness.h>
using namespace std;
using namespace gtsam;
// Set up initial pose, odometry difference, loop closure difference, and initialization errors
const Pose3 poseInitial;
const Pose3 poseOdometry( Rot3::RzRyRx(Vector_(3, 0.05, 0.10, -0.75)), Point3(1.0, -0.25, 0.10) );
const Pose3 poseError( Rot3::RzRyRx(Vector_(3, 0.01, 0.02, -0.1)), Point3(0.05, -0.05, 0.02) );
// Set up noise models for the factors
const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10);
const SharedDiagonal noiseOdometery = noiseModel::Diagonal::Sigmas(Vector_(6, 0.1, 0.1, 0.1, 0.5, 0.5, 0.5));
const SharedDiagonal noiseLoop = noiseModel::Diagonal::Sigmas(Vector_(6, 0.25, 0.25, 0.25, 1.0, 1.0, 1.0));
// Create a derived class to allow testing protected member functions
class ConcurrentBatchFilterTester : public ConcurrentBatchFilter {
public:
ConcurrentBatchFilterTester(const LevenbergMarquardtParams& parameters, double lag) : ConcurrentBatchFilter(parameters, lag) { };
virtual ~ConcurrentBatchFilterTester() { };
// Add accessors to the protected members
void presync() { ConcurrentBatchFilter::presync(); };
void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors, Values& rootValues) { ConcurrentBatchFilter::getSummarizedFactors(summarizedFactors, rootValues); };
void getSmootherFactors(NonlinearFactorGraph& smootherFactors, Values& smootherValues) { ConcurrentBatchFilter::getSmootherFactors(smootherFactors, smootherValues); };
void synchronize(const NonlinearFactorGraph& summarizedFactors) { ConcurrentBatchFilter::synchronize(summarizedFactors); };
void postsync() { ConcurrentBatchFilter::postsync(); };
};
/* ************************************************************************* */
bool hessian_equal(const NonlinearFactorGraph& expected, const NonlinearFactorGraph& actual, const Values& theta, double tol = 1e-9) {
FastSet<Key> expectedKeys = expected.keys();
FastSet<Key> actualKeys = actual.keys();
// Verify the set of keys in both graphs are the same
if(!std::equal(expectedKeys.begin(), expectedKeys.end(), actualKeys.begin()))
return false;
// Create an ordering
Ordering ordering;
BOOST_FOREACH(Key key, expectedKeys) {
ordering.push_back(key);
}
// Linearize each factor graph
GaussianFactorGraph expectedGaussian;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, expected) {
if(factor)
expectedGaussian.push_back( factor->linearize(theta, ordering) );
}
GaussianFactorGraph actualGaussian;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, actual) {
if(factor)
actualGaussian.push_back( factor->linearize(theta, ordering) );
}
// Convert linear factor graph into a dense Hessian
Matrix expectedHessian = expectedGaussian.augmentedHessian();
Matrix actualHessian = actualGaussian.augmentedHessian();
// Zero out the lower-right entry. This corresponds to a constant in the optimization,
// which does not affect the result. Further, in conversions between Jacobians and Hessians,
// this term is ignored.
expectedHessian(expectedHessian.rows()-1, expectedHessian.cols()-1) = 0.0;
actualHessian(actualHessian.rows()-1, actualHessian.cols()-1) = 0.0;
// Compare Hessians
return assert_equal(expectedHessian, actualHessian, tol);
}
///* ************************************************************************* */
void CreateFactors(NonlinearFactorGraph& graph, Values& theta, ConcurrentBatchFilter::KeyTimestampMap& timestamps, size_t index1 = 0, size_t index2 = 1) {
// Calculate all poses
Pose3 poses[20];
poses[0] = poseInitial;
for(size_t index = 1; index < 20; ++index) {
poses[index] = poses[index-1].compose(poseOdometry);
}
// Create all keys
Key keys[20];
for(size_t index = 0; index < 20; ++index) {
keys[index] = Symbol('X', index);
}
// Create factors that will form a specific tree structure
// Loop over the included timestamps
for(size_t index = index1; index < index2; ++index) {
switch(index) {
case 0:
{
graph.add(PriorFactor<Pose3>(keys[0], poses[0], noisePrior));
// Add new variables
theta.insert(keys[0], poses[0].compose(poseError));
timestamps[keys[0]] = double(0);
break;
}
case 1:
{
// Add odometry factor between 0 and 1
Pose3 poseDelta = poses[0].between(poses[1]);
graph.add(BetweenFactor<Pose3>(keys[0], keys[1], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[1], poses[1].compose(poseError));
timestamps[keys[1]] = double(1);
break;
}
case 2:
{
break;
}
case 3:
{
// Add odometry factor between 1 and 3
Pose3 poseDelta = poses[1].between(poses[3]);
graph.add(BetweenFactor<Pose3>(keys[1], keys[3], poseDelta, noiseOdometery));
// Add odometry factor between 2 and 3
poseDelta = poses[2].between(poses[3]);
graph.add(BetweenFactor<Pose3>(keys[2], keys[3], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[2], poses[2].compose(poseError));
timestamps[keys[2]] = double(2);
theta.insert(keys[3], poses[3].compose(poseError));
timestamps[keys[3]] = double(3);
break;
}
case 4:
{
break;
}
case 5:
{
// Add odometry factor between 3 and 5
Pose3 poseDelta = poses[3].between(poses[5]);
graph.add(BetweenFactor<Pose3>(keys[3], keys[5], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[5], poses[5].compose(poseError));
timestamps[keys[5]] = double(5);
break;
}
case 6:
{
// Add odometry factor between 3 and 6
Pose3 poseDelta = poses[3].between(poses[6]);
graph.add(BetweenFactor<Pose3>(keys[3], keys[6], poseDelta, noiseOdometery));
// Add odometry factor between 5 and 6
poseDelta = poses[5].between(poses[6]);
graph.add(BetweenFactor<Pose3>(keys[5], keys[6], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[6], poses[6].compose(poseError));
timestamps[keys[6]] = double(6);
break;
}
case 7:
{
// Add odometry factor between 4 and 7
Pose3 poseDelta = poses[4].between(poses[7]);
graph.add(BetweenFactor<Pose3>(keys[4], keys[7], poseDelta, noiseOdometery));
// Add odometry factor between 6 and 7
poseDelta = poses[6].between(poses[7]);
graph.add(BetweenFactor<Pose3>(keys[6], keys[7], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[4], poses[4].compose(poseError));
timestamps[keys[4]] = double(4);
theta.insert(keys[7], poses[7].compose(poseError));
timestamps[keys[7]] = double(7);
break;
}
case 8:
break;
case 9:
{
// Add odometry factor between 6 and 9
Pose3 poseDelta = poses[6].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[6], keys[9], poseDelta, noiseOdometery));
// Add odometry factor between 7 and 9
poseDelta = poses[7].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[7], keys[9], poseDelta, noiseOdometery));
// Add odometry factor between 8 and 9
poseDelta = poses[8].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[8], keys[9], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[8], poses[8].compose(poseError));
timestamps[keys[8]] = double(8);
theta.insert(keys[9], poses[9].compose(poseError));
timestamps[keys[9]] = double(9);
break;
}
case 10:
{
// Add odometry factor between 9 and 10
Pose3 poseDelta = poses[9].between(poses[10]);
graph.add(BetweenFactor<Pose3>(keys[9], keys[10], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[10], poses[10].compose(poseError));
timestamps[keys[10]] = double(10);
break;
}
case 11:
{
// Add odometry factor between 10 and 11
Pose3 poseDelta = poses[10].between(poses[11]);
graph.add(BetweenFactor<Pose3>(keys[10], keys[11], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[11], poses[11].compose(poseError));
timestamps[keys[11]] = double(11);
break;
}
case 12:
{
// Add odometry factor between 7 and 12
Pose3 poseDelta = poses[7].between(poses[12]);
graph.add(BetweenFactor<Pose3>(keys[7], keys[12], poseDelta, noiseOdometery));
// Add odometry factor between 9 and 12
poseDelta = poses[9].between(poses[12]);
graph.add(BetweenFactor<Pose3>(keys[9], keys[12], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[12], poses[12].compose(poseError));
timestamps[keys[12]] = double(12);
break;
}
case 13:
{
// Add odometry factor between 10 and 13
Pose3 poseDelta = poses[10].between(poses[13]);
graph.add(BetweenFactor<Pose3>(keys[10], keys[13], poseDelta, noiseOdometery));
// Add odometry factor between 12 and 13
poseDelta = poses[12].between(poses[13]);
graph.add(BetweenFactor<Pose3>(keys[12], keys[13], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[13], poses[13].compose(poseError));
timestamps[keys[13]] = double(13);
break;
}
case 14:
{
// Add odometry factor between 11 and 14
Pose3 poseDelta = poses[11].between(poses[14]);
graph.add(BetweenFactor<Pose3>(keys[11], keys[14], poseDelta, noiseOdometery));
// Add odometry factor between 13 and 14
poseDelta = poses[13].between(poses[14]);
graph.add(BetweenFactor<Pose3>(keys[13], keys[14], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[14], poses[14].compose(poseError));
timestamps[keys[14]] = double(14);
break;
}
case 15:
break;
case 16:
{
// Add odometry factor between 13 and 16
Pose3 poseDelta = poses[13].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[13], keys[16], poseDelta, noiseOdometery));
// Add odometry factor between 14 and 16
poseDelta = poses[14].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[14], keys[16], poseDelta, noiseOdometery));
// Add odometry factor between 15 and 16
poseDelta = poses[15].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[15], keys[16], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[15], poses[15].compose(poseError));
timestamps[keys[15]] = double(15);
theta.insert(keys[16], poses[16].compose(poseError));
timestamps[keys[16]] = double(16);
break;
}
case 17:
{
// Add odometry factor between 16 and 17
Pose3 poseDelta = poses[16].between(poses[17]);
graph.add(BetweenFactor<Pose3>(keys[16], keys[17], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[17], poses[17].compose(poseError));
timestamps[keys[17]] = double(17);
break;
}
case 18:
{
// Add odometry factor between 17 and 18
Pose3 poseDelta = poses[17].between(poses[18]);
graph.add(BetweenFactor<Pose3>(keys[17], keys[18], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[18], poses[18].compose(poseError));
timestamps[keys[18]] = double(18);
break;
}
case 19:
{
// Add odometry factor between 14 and 19
Pose3 poseDelta = poses[14].between(poses[19]);
graph.add(BetweenFactor<Pose3>(keys[14], keys[19], poseDelta, noiseOdometery));
// Add odometry factor between 16 and 19
poseDelta = poses[16].between(poses[19]);
graph.add(BetweenFactor<Pose3>(keys[16], keys[19], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[19], poses[19].compose(poseError));
timestamps[keys[19]] = double(19);
break;
}
}
}
return;
}
/* ************************************************************************* */
Values BatchOptimize(const NonlinearFactorGraph& graph, const Values& theta, const Values& rootValues = Values()) {
// Create an L-M optimizer
LevenbergMarquardtParams parameters;
parameters.linearSolverType = SuccessiveLinearizationParams::MULTIFRONTAL_QR;
LevenbergMarquardtOptimizer optimizer(graph, theta, parameters);
// Use a custom optimization loop so the linearization points can be controlled
double currentError;
do {
// Force variables associated with root keys to keep the same linearization point
if(rootValues.size() > 0) {
// Put the old values of the root keys back into the optimizer state
optimizer.state().values.update(rootValues);
// Update the error value with the new theta
optimizer.state().error = graph.error(optimizer.state().values);
}
// Do next iteration
currentError = optimizer.error();
optimizer.iterate();
} while(optimizer.iterations() < parameters.maxIterations &&
!checkConvergence(parameters.relativeErrorTol, parameters.absoluteErrorTol,
parameters.errorTol, currentError, optimizer.error(), parameters.verbosity));
// return the final optimized values
return optimizer.values();
}
/* ************************************************************************* */
void FindFactorsWithAny(const std::set<Key>& keys, const NonlinearFactorGraph& sourceFactors, NonlinearFactorGraph& destinationFactors) {
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, sourceFactors) {
NonlinearFactor::const_iterator key = factor->begin();
while((key != factor->end()) && (!std::binary_search(keys.begin(), keys.end(), *key))) {
++key;
}
if(key != factor->end()) {
destinationFactors.push_back(factor);
}
}
}
/* ************************************************************************* */
void FindFactorsWithOnly(const std::set<Key>& keys, const NonlinearFactorGraph& sourceFactors, NonlinearFactorGraph& destinationFactors) {
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, sourceFactors) {
NonlinearFactor::const_iterator key = factor->begin();
while((key != factor->end()) && (std::binary_search(keys.begin(), keys.end(), *key))) {
++key;
}
if(key == factor->end()) {
destinationFactors.push_back(factor);
}
}
}
/* ************************************************************************* */
typedef BayesTree<GaussianConditional,ISAM2Clique>::sharedClique Clique;
void SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent = "") {
std::cout << indent << "P( ";
BOOST_FOREACH(Index index, clique->conditional()->frontals()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
if(clique->conditional()->nrParents() > 0) {
std::cout << "| ";
}
BOOST_FOREACH(Index index, clique->conditional()->parents()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
BOOST_FOREACH(const Clique& child, clique->children()) {
SymbolicPrintTree(child, ordering, indent+" ");
}
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchFilter, update_Batch )
{
// Test the 'update' function of the ConcurrentBatchFilter in a nonlinear environment.
// Thus, a full L-M optimization and the ConcurrentBatchFilter results should be identical
// This tests adds all of the factors to the filter at once (i.e. batch)
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
double lag = 4.0;
// Create a Concurrent Batch Filter
ConcurrentBatchFilter filter(parameters, lag);
// Create containers to keep the full graph
Values fullTheta;
NonlinearFactorGraph fullGraph;
ConcurrentBatchFilter::KeyTimestampMap fullTimestamps;
// Create all factors
CreateFactors(fullGraph, fullTheta, fullTimestamps, 0, 20);
// Optimize with Concurrent Batch Filter
filter.update(fullGraph, fullTheta, fullTimestamps);
Values actual = filter.calculateEstimate();
// Optimize with L-M
Values expected = BatchOptimize(fullGraph, fullTheta);
// Check smoother versus batch
CHECK(assert_equal(expected, actual, 1e-4));
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchFilter, update_Incremental )
{
// Test the 'update' function of the ConcurrentBatchFilter in a nonlinear environment.
// Thus, a full L-M optimization and the ConcurrentBatchFilter results should be identical
// This tests adds the factors to the filter as they are created (i.e. incrementally)
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
double lag = 4.0;
// Create a Concurrent Batch Filter
ConcurrentBatchFilter filter(parameters, lag);
// Create containers to keep the full graph
Values fullTheta;
NonlinearFactorGraph fullGraph;
// Add odometry from time 0 to time 10
for(size_t i = 0; i < 20; ++i) {
// Create containers to keep the new factors
Values newTheta;
NonlinearFactorGraph newGraph;
ConcurrentBatchFilter::KeyTimestampMap newTimestamps;
// Create factors
CreateFactors(newGraph, newTheta, newTimestamps, i, i+1);
// Add these entries to the filter
filter.update(newGraph, newTheta, newTimestamps);
Values actual = filter.calculateEstimate();
// Add these entries to the full batch version
fullGraph.push_back(newGraph);
fullTheta.insert(newTheta);
Values expected = BatchOptimize(fullGraph, fullTheta);
fullTheta = expected;
// Compare filter solution with full batch
CHECK(assert_equal(expected, actual, 1e-4));
}
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchFilter, synchronize )
{
// Test the 'synchronize' function of the ConcurrentBatchFilter in a nonlinear environment.
// The filter is operating on a known tree structure, so the factors and summarization can
// be predicted for testing purposes
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
double lag = 4.0;
// Create a Concurrent Batch Filter
ConcurrentBatchFilterTester filter(parameters, lag);
// Create containers to keep the full graph
Values newTheta, fullTheta;
NonlinearFactorGraph newGraph, fullGraph;
ConcurrentBatchFilter::KeyTimestampMap newTimestamps;
// Create all factors
CreateFactors(newGraph, newTheta, newTimestamps, 0, 13);
fullTheta.insert(newTheta);
fullGraph.push_back(newGraph);
// Optimize with Concurrent Batch Filter
filter.update(newGraph, newTheta, newTimestamps);
Values updatedTheta = filter.calculateEstimate();
// Eliminate the factor graph into a Bayes Tree to create the summarized factors
// Create Ordering
std::map<Key, int> constraints;
constraints[Symbol('X', 8)] = 1;
constraints[Symbol('X', 9)] = 1;
constraints[Symbol('X', 10)] = 1;
constraints[Symbol('X', 11)] = 1;
constraints[Symbol('X', 12)] = 1;
Ordering ordering = *fullGraph.orderingCOLAMDConstrained(fullTheta, constraints);
// Linearize into a Gaussian Factor Graph
GaussianFactorGraph linearGraph = *fullGraph.linearize(fullTheta, ordering);
// Eliminate into a Bayes Net with iSAM2-type cliques
JunctionTree<GaussianFactorGraph, ISAM2Clique> jt(linearGraph);
ISAM2Clique::shared_ptr root = jt.eliminate(EliminateQR);
BayesTree<GaussianConditional, ISAM2Clique> bayesTree;
bayesTree.insert(root);
// std::cout << "BAYES TREE:" << std::endl;
// SymbolicPrintTree(root, ordering.invert(), " ");
// BAYES TREE:
// P( X7 X9 X12 )
// P( X10 | X9 )
// P( X11 | X10 )
// P( X8 | X9 )
// P( X6 | X7 X9 )
// P( X3 X5 | X6 )
// P( X2 | X3 )
// P( X1 | X3 )
// P( X0 | X1 )
// P( X4 | X7 )
// Extract the nonlinear factors that should be sent to the smoother
std::vector<Key> smootherKeys;
smootherKeys.push_back(Symbol('X', 0));
smootherKeys.push_back(Symbol('X', 1));
smootherKeys.push_back(Symbol('X', 2));
smootherKeys.push_back(Symbol('X', 3));
smootherKeys.push_back(Symbol('X', 4));
smootherKeys.push_back(Symbol('X', 5));
smootherKeys.push_back(Symbol('X', 6));
NonlinearFactorGraph expectedSmootherFactors;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, fullGraph) {
if(std::find_first_of(factor->begin(), factor->end(), smootherKeys.begin(), smootherKeys.end()) != factor->end()) {
expectedSmootherFactors.push_back(factor);
}
}
// Extract smoother values
Values expectedSmootherValues;
expectedSmootherValues.insert(Symbol('X', 0), updatedTheta.at(Symbol('X', 0)));
expectedSmootherValues.insert(Symbol('X', 1), updatedTheta.at(Symbol('X', 1)));
expectedSmootherValues.insert(Symbol('X', 2), updatedTheta.at(Symbol('X', 2)));
expectedSmootherValues.insert(Symbol('X', 3), updatedTheta.at(Symbol('X', 3)));
expectedSmootherValues.insert(Symbol('X', 4), updatedTheta.at(Symbol('X', 4)));
expectedSmootherValues.insert(Symbol('X', 5), updatedTheta.at(Symbol('X', 5)));
expectedSmootherValues.insert(Symbol('X', 6), updatedTheta.at(Symbol('X', 6)));
// Extract the filter summarized factors
// Cached factors that represent the filter side (i.e. the X8 and X10 clique)
NonlinearFactorGraph expectedFilterSumarization;
expectedFilterSumarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 8)))->cachedFactor()), ordering, fullTheta));
expectedFilterSumarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 10)))->cachedFactor()), ordering, fullTheta));
// And any factors that involve only the root (X7, X9, X12)
std::vector<Key> rootKeys;
rootKeys.push_back(Symbol('X', 7));
rootKeys.push_back(Symbol('X', 9));
rootKeys.push_back(Symbol('X', 12));
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, fullGraph) {
size_t count = 0;
BOOST_FOREACH(Key key, *factor) {
if(std::binary_search(rootKeys.begin(), rootKeys.end(), key)) ++count;
}
if(count == factor->size()) expectedFilterSumarization.push_back(factor);
}
// Extract the new root values
Values expectedRootValues;
expectedRootValues.insert(Symbol('X', 7), updatedTheta.at(Symbol('X', 7)));
expectedRootValues.insert(Symbol('X', 9), updatedTheta.at(Symbol('X', 9)));
expectedRootValues.insert(Symbol('X', 12), updatedTheta.at(Symbol('X', 12)));
// Start the synchronization process
NonlinearFactorGraph actualSmootherFactors, actualFilterSumarization, smootherSummarization;
Values actualSmootherValues, actualRootValues;
filter.presync();
filter.synchronize(smootherSummarization); // Supplying an empty factor graph here
filter.getSmootherFactors(actualSmootherFactors, actualSmootherValues);
filter.getSummarizedFactors(actualFilterSumarization, actualRootValues);
filter.postsync();
// Compare filter sync variables versus the expected
CHECK(hessian_equal(expectedSmootherFactors, actualSmootherFactors, updatedTheta, 1e-8));
CHECK(assert_equal(expectedSmootherValues, actualSmootherValues, 1e-4));
CHECK(hessian_equal(expectedFilterSumarization, actualFilterSumarization, updatedTheta, 1e-9));
CHECK(assert_equal(expectedRootValues, actualRootValues, 1e-4));
// Now add additional factors to the filter and re-sync
newGraph.resize(0); newTheta.clear(); newTimestamps.clear();
CreateFactors(newGraph, newTheta, newTimestamps, 13, 20);
fullTheta.insert(newTheta);
fullGraph.push_back(newGraph);
// Optimize with Concurrent Batch Filter
filter.update(newGraph, newTheta, newTimestamps);
updatedTheta = filter.calculateEstimate();
// Eliminate the factor graph into a Bayes Tree to create the summarized factors
// Create Ordering
constraints.clear();
constraints[Symbol('X', 15)] = 1;
constraints[Symbol('X', 16)] = 1;
constraints[Symbol('X', 17)] = 1;
constraints[Symbol('X', 18)] = 1;
constraints[Symbol('X', 19)] = 1;
ordering = *fullGraph.orderingCOLAMDConstrained(fullTheta, constraints);
// Linearize into a Gaussian Factor Graph
linearGraph = *fullGraph.linearize(fullTheta, ordering);
// Eliminate into a Bayes Net with iSAM2-type cliques
jt = JunctionTree<GaussianFactorGraph, ISAM2Clique>(linearGraph);
root = jt.eliminate(EliminateQR);
bayesTree = BayesTree<GaussianConditional, ISAM2Clique>();
bayesTree.insert(root);
// std::cout << "BAYES TREE:" << std::endl;
// SymbolicPrintTree(root, ordering.invert(), " ");
// BAYES TREE:
// P( X14 X16 X19 )
// P( X17 | X16 )
// P( X18 | X17 )
// P( X15 | X16 )
// P( X13 | X14 X16 )
// P( X11 | X13 X14 )
// P( X10 | X11 X13 )
// P( X12 | X10 X13 )
// P( X9 | X12 X10 )
// P( X7 | X9 X12 )
// P( X6 | X7 X9 )
// P( X3 X5 | X6 )
// P( X2 | X3 )
// P( X1 | X3 )
// P( X0 | X1 )
// P( X4 | X7 )
// P( X8 | X9 )
// Extract the cached factors for X4 and X6. These are the new smoother summarized factors
// TODO: I'm concerned about the linearization point used to create these factors. It may need to be the updated lin points?
smootherSummarization.resize(0);
smootherSummarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 4)))->cachedFactor()), ordering, fullTheta));
smootherSummarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 6)))->cachedFactor()), ordering, fullTheta));
// Extract the nonlinear factors that should be sent to the smoother
smootherKeys.clear();
smootherKeys.push_back(Symbol('X', 8));
smootherKeys.push_back(Symbol('X', 10));
smootherKeys.push_back(Symbol('X', 11));
smootherKeys.push_back(Symbol('X', 13));
expectedSmootherFactors.resize(0);
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, fullGraph) {
if(std::find_first_of(factor->begin(), factor->end(), smootherKeys.begin(), smootherKeys.end()) != factor->end()) {
expectedSmootherFactors.push_back(factor);
}
}
// And any factors that involve only the old root (X7, X9, X12)
rootKeys.clear();
rootKeys.push_back(Symbol('X', 7));
rootKeys.push_back(Symbol('X', 9));
rootKeys.push_back(Symbol('X', 12));
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, fullGraph) {
size_t count = 0;
BOOST_FOREACH(Key key, *factor) {
if(std::binary_search(rootKeys.begin(), rootKeys.end(), key)) ++count;
}
if(count == factor->size()) expectedSmootherFactors.push_back(factor);
}
// Extract smoother Values
expectedSmootherValues.clear();
expectedSmootherValues.insert(Symbol('X', 7), updatedTheta.at(Symbol('X', 7)));
expectedSmootherValues.insert(Symbol('X', 8), updatedTheta.at(Symbol('X', 8)));
expectedSmootherValues.insert(Symbol('X', 9), updatedTheta.at(Symbol('X', 9)));
expectedSmootherValues.insert(Symbol('X', 10), updatedTheta.at(Symbol('X', 10)));
expectedSmootherValues.insert(Symbol('X', 11), updatedTheta.at(Symbol('X', 11)));
expectedSmootherValues.insert(Symbol('X', 12), updatedTheta.at(Symbol('X', 12)));
expectedSmootherValues.insert(Symbol('X', 13), updatedTheta.at(Symbol('X', 13)));
// Extract the filter summarized factors
// Cached factors that represent the filter side (i.e. the X15 and X17 clique)
expectedFilterSumarization.resize(0);
expectedFilterSumarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 15)))->cachedFactor()), ordering, fullTheta));
expectedFilterSumarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 17)))->cachedFactor()), ordering, fullTheta));
// And any factors that involve only the root (X14, X16, X17)
rootKeys.clear();
rootKeys.push_back(Symbol('X', 14));
rootKeys.push_back(Symbol('X', 16));
rootKeys.push_back(Symbol('X', 19));
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, fullGraph) {
size_t count = 0;
BOOST_FOREACH(Key key, *factor) {
if(std::binary_search(rootKeys.begin(), rootKeys.end(), key)) ++count;
}
if(count == factor->size()) expectedFilterSumarization.push_back(factor);
}
// Extract the new root keys
expectedRootValues.clear();
expectedRootValues.insert(Symbol('X', 14), updatedTheta.at(Symbol('X', 14)));
expectedRootValues.insert(Symbol('X', 16), updatedTheta.at(Symbol('X', 16)));
expectedRootValues.insert(Symbol('X', 19), updatedTheta.at(Symbol('X', 19)));
// Start the synchronization process
actualSmootherFactors.resize(0); actualFilterSumarization.resize(0);
actualSmootherValues.clear(); actualRootValues.clear();
filter.presync();
filter.synchronize(smootherSummarization);
filter.getSmootherFactors(actualSmootherFactors, actualSmootherValues);
filter.getSummarizedFactors(actualFilterSumarization, actualRootValues);
filter.postsync();
// Compare filter sync variables versus the expected
CHECK(hessian_equal(expectedSmootherFactors, actualSmootherFactors, updatedTheta, 1e-8));
CHECK(assert_equal(expectedSmootherValues, actualSmootherValues, 1e-4));
CHECK(hessian_equal(expectedFilterSumarization, actualFilterSumarization, updatedTheta, 1e-8));
CHECK(assert_equal(expectedRootValues, actualRootValues, 1e-4));
}
/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr);}
/* ************************************************************************* */

View File

@ -0,0 +1,708 @@
/* ----------------------------------------------------------------------------
* 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 testConcurrentBatchSmoother.cpp
* @brief Unit tests for the Concurrent Batch Smoother
* @author Stephen Williams (swilliams8@gatech.edu)
* @date Jan 5, 2013
*/
#include <gtsam_unstable/nonlinear/ConcurrentBatchSmoother.h>
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/nonlinear/ISAM2.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Ordering.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/nonlinear/Symbol.h>
#include <gtsam/nonlinear/Key.h>
#include <gtsam/inference/JunctionTree.h>
#include <gtsam/geometry/Pose3.h>
#include <CppUnitLite/TestHarness.h>
using namespace std;
using namespace gtsam;
// Set up initial pose, odometry difference, loop closure difference, and initialization errors
const Pose3 poseInitial;
const Pose3 poseOdometry( Rot3::RzRyRx(Vector_(3, 0.05, 0.10, -0.75)), Point3(1.0, -0.25, 0.10) );
const Pose3 poseError( Rot3::RzRyRx(Vector_(3, 0.01, 0.02, -0.1)), Point3(0.05, -0.05, 0.02) );
// Set up noise models for the factors
const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10);
const SharedDiagonal noiseOdometery = noiseModel::Diagonal::Sigmas(Vector_(6, 0.1, 0.1, 0.1, 0.5, 0.5, 0.5));
const SharedDiagonal noiseLoop = noiseModel::Diagonal::Sigmas(Vector_(6, 0.25, 0.25, 0.25, 1.0, 1.0, 1.0));
// Create a derived class to allow testing protected member functions
class ConcurrentBatchSmootherTester : public ConcurrentBatchSmoother {
public:
ConcurrentBatchSmootherTester(const LevenbergMarquardtParams& parameters) : ConcurrentBatchSmoother(parameters) { };
virtual ~ConcurrentBatchSmootherTester() { };
// Add accessors to the protected members
void presync() {
ConcurrentBatchSmoother::presync();
};
void getSummarizedFactors(NonlinearFactorGraph& summarizedFactors) {
ConcurrentBatchSmoother::getSummarizedFactors(summarizedFactors);
};
void synchronize(const NonlinearFactorGraph& smootherFactors, const Values& smootherValues, const NonlinearFactorGraph& summarizedFactors, const Values& rootValues) {
ConcurrentBatchSmoother::synchronize(smootherFactors, smootherValues, summarizedFactors, rootValues);
};
void postsync() {
ConcurrentBatchSmoother::postsync();
};
};
/* ************************************************************************* */
bool hessian_equal(const NonlinearFactorGraph& expected, const NonlinearFactorGraph& actual, const Values& theta, double tol = 1e-9) {
FastSet<Key> expectedKeys = expected.keys();
FastSet<Key> actualKeys = actual.keys();
// Verify the set of keys in both graphs are the same
if(!std::equal(expectedKeys.begin(), expectedKeys.end(), actualKeys.begin()))
return false;
// Create an ordering
Ordering ordering;
BOOST_FOREACH(Key key, expectedKeys) {
ordering.push_back(key);
}
// Linearize each factor graph
GaussianFactorGraph expectedGaussian;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, expected) {
if(factor)
expectedGaussian.push_back( factor->linearize(theta, ordering) );
}
GaussianFactorGraph actualGaussian;
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, actual) {
if(factor)
actualGaussian.push_back( factor->linearize(theta, ordering) );
}
// Convert linear factor graph into a dense Hessian
Matrix expectedHessian = expectedGaussian.augmentedHessian();
Matrix actualHessian = actualGaussian.augmentedHessian();
// Zero out the lower-right entry. This corresponds to a constant in the optimization,
// which does not affect the result. Further, in conversions between Jacobians and Hessians,
// this term is ignored.
expectedHessian(expectedHessian.rows()-1, expectedHessian.cols()-1) = 0.0;
actualHessian(actualHessian.rows()-1, actualHessian.cols()-1) = 0.0;
// Compare Hessians
return assert_equal(expectedHessian, actualHessian, tol);
}
///* ************************************************************************* */
void CreateFactors(NonlinearFactorGraph& graph, Values& theta, size_t index1 = 0, size_t index2 = 1) {
// Calculate all poses
Pose3 poses[20];
poses[0] = poseInitial;
for(size_t index = 1; index < 20; ++index) {
poses[index] = poses[index-1].compose(poseOdometry);
}
// Create all keys
Key keys[20];
for(size_t index = 0; index < 20; ++index) {
keys[index] = Symbol('X', index);
}
// Create factors that will form a specific tree structure
// Loop over the included timestamps
for(size_t index = index1; index < index2; ++index) {
switch(index) {
case 0:
{
graph.add(PriorFactor<Pose3>(keys[0], poses[0], noisePrior));
// Add new variables
theta.insert(keys[0], poses[0].compose(poseError));
break;
}
case 1:
{
// Add odometry factor between 0 and 1
Pose3 poseDelta = poses[0].between(poses[1]);
graph.add(BetweenFactor<Pose3>(keys[0], keys[1], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[1], poses[1].compose(poseError));
break;
}
case 2:
{
break;
}
case 3:
{
// Add odometry factor between 1 and 3
Pose3 poseDelta = poses[1].between(poses[3]);
graph.add(BetweenFactor<Pose3>(keys[1], keys[3], poseDelta, noiseOdometery));
// Add odometry factor between 2 and 3
poseDelta = poses[2].between(poses[3]);
graph.add(BetweenFactor<Pose3>(keys[2], keys[3], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[2], poses[2].compose(poseError));
theta.insert(keys[3], poses[3].compose(poseError));
break;
}
case 4:
{
break;
}
case 5:
{
// Add odometry factor between 3 and 5
Pose3 poseDelta = poses[3].between(poses[5]);
graph.add(BetweenFactor<Pose3>(keys[3], keys[5], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[5], poses[5].compose(poseError));
break;
}
case 6:
{
// Add odometry factor between 3 and 6
Pose3 poseDelta = poses[3].between(poses[6]);
graph.add(BetweenFactor<Pose3>(keys[3], keys[6], poseDelta, noiseOdometery));
// Add odometry factor between 5 and 6
poseDelta = poses[5].between(poses[6]);
graph.add(BetweenFactor<Pose3>(keys[5], keys[6], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[6], poses[6].compose(poseError));
break;
}
case 7:
{
// Add odometry factor between 4 and 7
Pose3 poseDelta = poses[4].between(poses[7]);
graph.add(BetweenFactor<Pose3>(keys[4], keys[7], poseDelta, noiseOdometery));
// Add odometry factor between 6 and 7
poseDelta = poses[6].between(poses[7]);
graph.add(BetweenFactor<Pose3>(keys[6], keys[7], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[4], poses[4].compose(poseError));
theta.insert(keys[7], poses[7].compose(poseError));
break;
}
case 8:
break;
case 9:
{
// Add odometry factor between 6 and 9
Pose3 poseDelta = poses[6].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[6], keys[9], poseDelta, noiseOdometery));
// Add odometry factor between 7 and 9
poseDelta = poses[7].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[7], keys[9], poseDelta, noiseOdometery));
// Add odometry factor between 8 and 9
poseDelta = poses[8].between(poses[9]);
graph.add(BetweenFactor<Pose3>(keys[8], keys[9], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[8], poses[8].compose(poseError));
theta.insert(keys[9], poses[9].compose(poseError));
break;
}
case 10:
{
// Add odometry factor between 9 and 10
Pose3 poseDelta = poses[9].between(poses[10]);
graph.add(BetweenFactor<Pose3>(keys[9], keys[10], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[10], poses[10].compose(poseError));
break;
}
case 11:
{
// Add odometry factor between 10 and 11
Pose3 poseDelta = poses[10].between(poses[11]);
graph.add(BetweenFactor<Pose3>(keys[10], keys[11], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[11], poses[11].compose(poseError));
break;
}
case 12:
{
// Add odometry factor between 7 and 12
Pose3 poseDelta = poses[7].between(poses[12]);
graph.add(BetweenFactor<Pose3>(keys[7], keys[12], poseDelta, noiseOdometery));
// Add odometry factor between 9 and 12
poseDelta = poses[9].between(poses[12]);
graph.add(BetweenFactor<Pose3>(keys[9], keys[12], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[12], poses[12].compose(poseError));
break;
}
case 13:
{
// Add odometry factor between 10 and 13
Pose3 poseDelta = poses[10].between(poses[13]);
graph.add(BetweenFactor<Pose3>(keys[10], keys[13], poseDelta, noiseOdometery));
// Add odometry factor between 12 and 13
poseDelta = poses[12].between(poses[13]);
graph.add(BetweenFactor<Pose3>(keys[12], keys[13], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[13], poses[13].compose(poseError));
break;
}
case 14:
{
// Add odometry factor between 11 and 14
Pose3 poseDelta = poses[11].between(poses[14]);
graph.add(BetweenFactor<Pose3>(keys[11], keys[14], poseDelta, noiseOdometery));
// Add odometry factor between 13 and 14
poseDelta = poses[13].between(poses[14]);
graph.add(BetweenFactor<Pose3>(keys[13], keys[14], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[14], poses[14].compose(poseError));
break;
}
case 15:
break;
case 16:
{
// Add odometry factor between 13 and 16
Pose3 poseDelta = poses[13].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[13], keys[16], poseDelta, noiseOdometery));
// Add odometry factor between 14 and 16
poseDelta = poses[14].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[14], keys[16], poseDelta, noiseOdometery));
// Add odometry factor between 15 and 16
poseDelta = poses[15].between(poses[16]);
graph.add(BetweenFactor<Pose3>(keys[15], keys[16], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[15], poses[15].compose(poseError));
theta.insert(keys[16], poses[16].compose(poseError));
break;
}
case 17:
{
// Add odometry factor between 16 and 17
Pose3 poseDelta = poses[16].between(poses[17]);
graph.add(BetweenFactor<Pose3>(keys[16], keys[17], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[17], poses[17].compose(poseError));
break;
}
case 18:
{
// Add odometry factor between 17 and 18
Pose3 poseDelta = poses[17].between(poses[18]);
graph.add(BetweenFactor<Pose3>(keys[17], keys[18], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[18], poses[18].compose(poseError));
break;
}
case 19:
{
// Add odometry factor between 14 and 19
Pose3 poseDelta = poses[14].between(poses[19]);
graph.add(BetweenFactor<Pose3>(keys[14], keys[19], poseDelta, noiseOdometery));
// Add odometry factor between 16 and 19
poseDelta = poses[16].between(poses[19]);
graph.add(BetweenFactor<Pose3>(keys[16], keys[19], poseDelta, noiseOdometery));
// Add new variables
theta.insert(keys[19], poses[19].compose(poseError));
break;
}
}
}
return;
}
/* ************************************************************************* */
Values BatchOptimize(const NonlinearFactorGraph& graph, const Values& theta, const Values& rootValues = Values()) {
// Create an L-M optimizer
LevenbergMarquardtParams parameters;
parameters.linearSolverType = SuccessiveLinearizationParams::MULTIFRONTAL_QR;
LevenbergMarquardtOptimizer optimizer(graph, theta, parameters);
// Use a custom optimization loop so the linearization points can be controlled
double currentError;
do {
// Force variables associated with root keys to keep the same linearization point
if(rootValues.size() > 0) {
// Put the old values of the root keys back into the optimizer state
optimizer.state().values.update(rootValues);
// Update the error value with the new theta
optimizer.state().error = graph.error(optimizer.state().values);
}
// Do next iteration
currentError = optimizer.error();
optimizer.iterate();
} while(optimizer.iterations() < parameters.maxIterations &&
!checkConvergence(parameters.relativeErrorTol, parameters.absoluteErrorTol,
parameters.errorTol, currentError, optimizer.error(), parameters.verbosity));
// return the final optimized values
return optimizer.values();
}
/* ************************************************************************* */
void FindFactorsWithAny(const std::set<Key>& keys, const NonlinearFactorGraph& sourceFactors, NonlinearFactorGraph& destinationFactors) {
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, sourceFactors) {
NonlinearFactor::const_iterator key = factor->begin();
while((key != factor->end()) && (!std::binary_search(keys.begin(), keys.end(), *key))) {
++key;
}
if(key != factor->end()) {
destinationFactors.push_back(factor);
}
}
}
/* ************************************************************************* */
void FindFactorsWithOnly(const std::set<Key>& keys, const NonlinearFactorGraph& sourceFactors, NonlinearFactorGraph& destinationFactors) {
BOOST_FOREACH(const NonlinearFactor::shared_ptr& factor, sourceFactors) {
NonlinearFactor::const_iterator key = factor->begin();
while((key != factor->end()) && (std::binary_search(keys.begin(), keys.end(), *key))) {
++key;
}
if(key == factor->end()) {
destinationFactors.push_back(factor);
}
}
}
/* ************************************************************************* */
typedef BayesTree<GaussianConditional,ISAM2Clique>::sharedClique Clique;
void SymbolicPrintTree(const Clique& clique, const Ordering& ordering, const std::string indent = "") {
std::cout << indent << "P( ";
BOOST_FOREACH(Index index, clique->conditional()->frontals()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
if(clique->conditional()->nrParents() > 0) {
std::cout << "| ";
}
BOOST_FOREACH(Index index, clique->conditional()->parents()){
std::cout << DefaultKeyFormatter(ordering.key(index)) << " ";
}
std::cout << ")" << std::endl;
BOOST_FOREACH(const Clique& child, clique->children()) {
SymbolicPrintTree(child, ordering, indent+" ");
}
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchSmoother, update_Batch )
{
// Test the 'update' function of the ConcurrentBatchSmoother in a nonlinear environment.
// Thus, a full L-M optimization and the ConcurrentBatchSmoother results should be identical
// This tests adds all of the factors to the smoother at once (i.e. batch)
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
// Create a Concurrent Batch Smoother
ConcurrentBatchSmoother smoother(parameters);
// Create containers to keep the full graph
Values fullTheta;
NonlinearFactorGraph fullGraph;
// Create all factors
CreateFactors(fullGraph, fullTheta, 0, 20);
// Optimize with Concurrent Batch Smoother
smoother.update(fullGraph, fullTheta);
Values actual = smoother.calculateEstimate();
// Optimize with L-M
Values expected = BatchOptimize(fullGraph, fullTheta);
// Check smoother versus batch
CHECK(assert_equal(expected, actual, 1e-4));
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchSmoother, update_Incremental )
{
// Test the 'update' function of the ConcurrentBatchSmoother in a nonlinear environment.
// Thus, a full L-M optimization and the ConcurrentBatchSmoother results should be identical
// This tests adds the factors to the smoother as they are created (i.e. incrementally)
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
// Create a Concurrent Batch Smoother
ConcurrentBatchSmoother smoother(parameters);
// Create containers to keep the full graph
Values fullTheta;
NonlinearFactorGraph fullGraph;
// Add odometry from time 0 to time 10
for(size_t i = 0; i < 20; ++i) {
// Create containers to keep the new factors
Values newTheta;
NonlinearFactorGraph newGraph;
// Create factors
CreateFactors(newGraph, newTheta, i, i+1);
// Add these entries to the filter
smoother.update(newGraph, newTheta);
Values actual = smoother.calculateEstimate();
// Add these entries to the full batch version
fullGraph.push_back(newGraph);
fullTheta.insert(newTheta);
Values expected = BatchOptimize(fullGraph, fullTheta);
fullTheta = expected;
// Compare filter solution with full batch
CHECK(assert_equal(expected, actual, 1e-4));
}
}
/* ************************************************************************* */
TEST_UNSAFE( ConcurrentBatchSmoother, synchronize )
{
// Test the 'synchronize' function of the ConcurrentBatchSmoother in a nonlinear environment.
// The smoother is operating on a known tree structure, so the factors and summarization can
// be predicted for testing purposes
// Create a set of optimizer parameters
LevenbergMarquardtParams parameters;
// Create a Concurrent Batch Smoother
ConcurrentBatchSmootherTester smoother(parameters);
// Create containers to keep the full graph
Values fullTheta;
NonlinearFactorGraph fullGraph;
// Create factors for times 0 - 12
// When eliminated with ordering (X2 X0 X1 X4 X5 X3 X6 X8 X11 X10 X7 X9 X12)augmentedHessian
// ... this Bayes Tree is produced:
// Bayes Tree:
// P( X7 X9 X12 )
// P( X10 | X9 )
// P( X11 | X10 )
// P( X8 | X9 )
// P( X6 | X7 X9 )
// P( X5 X3 | X6 )
// P( X1 | X3 )
// P( X0 | X1 )
// P( X2 | X3 )
// P( X4 | X7 )
// We then produce the inputs necessary for the 'synchronize' function.
// The smoother is branches X4 and X6, the filter is branches X8 and X10, and the root is (X7 X9 X12)
CreateFactors(fullGraph, fullTheta, 0, 13);
// Optimize the full graph
Values optimalTheta = BatchOptimize(fullGraph, fullTheta);
// Re-eliminate to create the Bayes Tree
Ordering ordering;
ordering.push_back(Symbol('X', 2));
ordering.push_back(Symbol('X', 0));
ordering.push_back(Symbol('X', 1));
ordering.push_back(Symbol('X', 4));
ordering.push_back(Symbol('X', 5));
ordering.push_back(Symbol('X', 3));
ordering.push_back(Symbol('X', 6));
ordering.push_back(Symbol('X', 8));
ordering.push_back(Symbol('X', 11));
ordering.push_back(Symbol('X', 10));
ordering.push_back(Symbol('X', 7));
ordering.push_back(Symbol('X', 9));
ordering.push_back(Symbol('X', 12));
Values linpoint;
linpoint.insert(optimalTheta);
GaussianFactorGraph linearGraph = *fullGraph.linearize(linpoint, ordering);
JunctionTree<GaussianFactorGraph, ISAM2Clique> jt(linearGraph);
ISAM2Clique::shared_ptr root = jt.eliminate(EliminateQR);
BayesTree<GaussianConditional, ISAM2Clique> bayesTree;
bayesTree.insert(root);
// Extract the values for the smoother keys. This consists of the branches: X4 and X6
// Extract the non-root values from the initial values to test the smoother optimization
Values smootherValues;
smootherValues.insert(Symbol('X', 0), fullTheta.at(Symbol('X', 0)));
smootherValues.insert(Symbol('X', 1), fullTheta.at(Symbol('X', 1)));
smootherValues.insert(Symbol('X', 2), fullTheta.at(Symbol('X', 2)));
smootherValues.insert(Symbol('X', 3), fullTheta.at(Symbol('X', 3)));
smootherValues.insert(Symbol('X', 4), fullTheta.at(Symbol('X', 4)));
smootherValues.insert(Symbol('X', 5), fullTheta.at(Symbol('X', 5)));
smootherValues.insert(Symbol('X', 6), fullTheta.at(Symbol('X', 6)));
// Extract the optimal root values
Values rootValues;
rootValues.insert(Symbol('X', 7), optimalTheta.at(Symbol('X', 7)));
rootValues.insert(Symbol('X', 9), optimalTheta.at(Symbol('X', 9)));
rootValues.insert(Symbol('X', 12), optimalTheta.at(Symbol('X', 12)));
// Extract the nonlinear smoother factors as any factor with a non-root smoother key
std::set<Key> smootherKeys;
smootherKeys.insert(Symbol('X', 0));
smootherKeys.insert(Symbol('X', 1));
smootherKeys.insert(Symbol('X', 2));
smootherKeys.insert(Symbol('X', 3));
smootherKeys.insert(Symbol('X', 4));
smootherKeys.insert(Symbol('X', 5));
smootherKeys.insert(Symbol('X', 6));
NonlinearFactorGraph smootherFactors;
FindFactorsWithAny(smootherKeys, fullGraph, smootherFactors);
// Extract the filter summarized factors. This consists of the linear cached factors from
// the filter branches X8 and X10, as well as any nonlinear factor that involves only root keys
NonlinearFactorGraph filterSummarization;
filterSummarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 8)))->cachedFactor()), ordering, linpoint));
filterSummarization.add(LinearizedJacobianFactor(boost::static_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 10)))->cachedFactor()), ordering, linpoint));
std::set<Key> rootKeys;
rootKeys.insert(Symbol('X', 7));
rootKeys.insert(Symbol('X', 9));
rootKeys.insert(Symbol('X', 12));
FindFactorsWithOnly(rootKeys, fullGraph, filterSummarization);
// Perform the synchronization procedure
NonlinearFactorGraph actualSmootherSummarization;
smoother.presync();
smoother.getSummarizedFactors(actualSmootherSummarization);
smoother.synchronize(smootherFactors, smootherValues, filterSummarization, rootValues);
smoother.postsync();
// Verify the returned smoother values is empty in the first iteration
NonlinearFactorGraph expectedSmootherSummarization;
CHECK(assert_equal(expectedSmootherSummarization, actualSmootherSummarization, 1e-4));
// Perform a full update of the smoother. Since the root values/summarized filter factors were
// created at the optimal values, the smoother should be identical to the batch optimization
smoother.update();
Values actualSmootherTheta = smoother.calculateEstimate();
// Create the expected values as the optimal set
Values expectedSmootherTheta;
expectedSmootherTheta.insert(Symbol('X', 0), optimalTheta.at(Symbol('X', 0)));
expectedSmootherTheta.insert(Symbol('X', 1), optimalTheta.at(Symbol('X', 1)));
expectedSmootherTheta.insert(Symbol('X', 2), optimalTheta.at(Symbol('X', 2)));
expectedSmootherTheta.insert(Symbol('X', 3), optimalTheta.at(Symbol('X', 3)));
expectedSmootherTheta.insert(Symbol('X', 4), optimalTheta.at(Symbol('X', 4)));
expectedSmootherTheta.insert(Symbol('X', 5), optimalTheta.at(Symbol('X', 5)));
expectedSmootherTheta.insert(Symbol('X', 6), optimalTheta.at(Symbol('X', 6)));
// Compare filter solution with full batch
CHECK(assert_equal(expectedSmootherTheta, actualSmootherTheta, 1e-4));
// Add a loop closure factor to the smoother and re-check. Since the filter
// factors were created at the optimal linpoint, and since the new loop closure
// does not involve filter keys, the smoother should still yeild the optimal solution
// The new Bayes Tree is:
// Bayes Tree:
// P( X7 X9 X12 )
// P( X10 | X9 )
// P( X11 | X10 )
// P( X8 | X9 )
// P( X6 | X7 X9 )
// P( X4 | X6 X7 )
// P( X3 X5 | X4 X6 )
// P( X2 | X3 )
// P( X1 | X3 X4 )
// P( X0 | X1 )
Pose3 poseDelta = fullTheta.at<Pose3>(Symbol('X', 1)).between(fullTheta.at<Pose3>(Symbol('X', 4)));
NonlinearFactor::shared_ptr loopClosure = NonlinearFactor::shared_ptr(new BetweenFactor<Pose3>(Symbol('X', 1), Symbol('X', 4), poseDelta, noiseOdometery));
fullGraph.push_back(loopClosure);
optimalTheta = BatchOptimize(fullGraph, fullTheta, rootValues);
// Recreate the Bayes Tree
linpoint.clear();
linpoint.insert(optimalTheta);
linpoint.update(rootValues);
linearGraph = *fullGraph.linearize(linpoint, ordering);
jt = JunctionTree<GaussianFactorGraph, ISAM2Clique>(linearGraph);
root = jt.eliminate(EliminateQR);
bayesTree = BayesTree<GaussianConditional, ISAM2Clique>();
bayesTree.insert(root);
// Add the loop closure to the smoother
NonlinearFactorGraph newFactors;
newFactors.push_back(loopClosure);
smoother.update(newFactors);
actualSmootherTheta = smoother.calculateEstimate();
// Create the expected values as the optimal set
expectedSmootherTheta.clear();
expectedSmootherTheta.insert(Symbol('X', 0), optimalTheta.at(Symbol('X', 0)));
expectedSmootherTheta.insert(Symbol('X', 1), optimalTheta.at(Symbol('X', 1)));
expectedSmootherTheta.insert(Symbol('X', 2), optimalTheta.at(Symbol('X', 2)));
expectedSmootherTheta.insert(Symbol('X', 3), optimalTheta.at(Symbol('X', 3)));
expectedSmootherTheta.insert(Symbol('X', 4), optimalTheta.at(Symbol('X', 4)));
expectedSmootherTheta.insert(Symbol('X', 5), optimalTheta.at(Symbol('X', 5)));
expectedSmootherTheta.insert(Symbol('X', 6), optimalTheta.at(Symbol('X', 6)));
// Compare filter solution with full batch
// TODO: Check This
// CHECK(assert_equal(expectedSmootherTheta, actualSmootherTheta, 1e-4));
// Now perform a second synchronization to test the smoother-calculated summarization
actualSmootherSummarization.resize(0);
smootherFactors.resize(0);
smootherValues.clear();
smoother.presync();
smoother.getSummarizedFactors(actualSmootherSummarization);
smoother.synchronize(smootherFactors, smootherValues, filterSummarization, rootValues);
smoother.postsync();
// Extract the expected smoother summarization from the Bayes Tree
// The smoother branches after the addition of the loop closure is only X6
expectedSmootherSummarization.resize(0);
JacobianFactor::shared_ptr jf = boost::dynamic_pointer_cast<JacobianFactor>(bayesTree.nodes().at(ordering.at(Symbol('X', 6)))->cachedFactor());
LinearizedJacobianFactor::shared_ptr ljf(new LinearizedJacobianFactor(jf, ordering, linpoint));
expectedSmootherSummarization.push_back(ljf);
// Compare smoother factors with the expected factors by computing the hessian information matrix
// TODO: Check This
// CHECK(hessian_equal(expectedSmootherSummarization, actualSmootherSummarization, linpoint, 1e-4));
// TODO: Modify the second synchronization so that the filter sends an additional set of factors.
// I'm not sure what additional code this will exercise, but just for good measure.
}
/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr);}
/* ************************************************************************* */

View File

@ -0,0 +1,316 @@
/* ----------------------------------------------------------------------------
* 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 testRFID.cpp
* @brief Unit tests for the RFID factor
* @author Stephen Williams (swilliams8@gatech.edu)
* @date Jan 16, 2012
*/
#include <gtsam_unstable/nonlinear/LinearizedFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/geometry/Point3.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/nonlinear/Key.h>
#include <gtsam/base/numericalDerivative.h>
#include <CppUnitLite/TestHarness.h>
using namespace gtsam;
/* ************************************************************************* */
TEST( LinearizedFactor, equals_jacobian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create two identical factors and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
LinearizedJacobianFactor jacobian1(jf, ordering, values);
LinearizedJacobianFactor jacobian2(jf, ordering, values);
CHECK(assert_equal(jacobian1, jacobian2));
}
/* ************************************************************************* */
TEST( LinearizedFactor, clone_jacobian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create one factor that is a clone of the other and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
LinearizedJacobianFactor jacobian1(jf, ordering, values);
LinearizedJacobianFactor::shared_ptr jacobian2 = boost::static_pointer_cast<LinearizedJacobianFactor>(jacobian1.clone());
CHECK(assert_equal(jacobian1, *jacobian2));
JacobianFactor::shared_ptr jf1 = boost::static_pointer_cast<JacobianFactor>(jacobian1.linearize(values, ordering));
JacobianFactor::shared_ptr jf2 = boost::static_pointer_cast<JacobianFactor>(jacobian2->linearize(values, ordering));
CHECK(assert_equal(*jf1, *jf2));
Matrix information1 = jf1->augmentedInformation();
Matrix information2 = jf2->augmentedInformation();
CHECK(assert_equal(information1, information2));
}
/* ************************************************************************* */
TEST( LinearizedFactor, add_jacobian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create two factor graphs, one using 'push_back' and one using 'add' and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
LinearizedJacobianFactor::shared_ptr jacobian(new LinearizedJacobianFactor(jf, ordering, values));
NonlinearFactorGraph graph1; graph1.push_back(jacobian);
NonlinearFactorGraph graph2; graph2.add(*jacobian);
// TODO: When creating a Jacobian from a cached factor, I experienced a problem in the 'add' version
// However, I am currently unable to reproduce the error in this unit test.
// I don't know if this affects the Hessian version as well.
CHECK(assert_equal(graph1, graph2));
}
///* ************************************************************************* */
//TEST( LinearizedFactor, error_jacobian )
//{
// // Create a Between Factor from a Point3. This is actually a linear factor.
// Key key1(1);
// Key key2(2);
// Ordering ordering;
// ordering.push_back(key1);
// ordering.push_back(key2);
// Values values;
// values.insert(key1, Point3(-22.4, +8.5, +2.4));
// values.insert(key2, Point3(-21.0, +5.0, +21.0));
//
// Point3 measured(1.0, -2.5, 17.8);
// SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
// BetweenFactor<Point3> betweenFactor(key1, key2, measured, model);
//
//
// // Create a linearized jacobian factors
// JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
// LinearizedJacobianFactor jacobian(jf, ordering, values);
//
//
// for(double x1 = -10; x1 < 10; x1 += 2.0) {
// for(double y1 = -10; y1 < 10; y1 += 2.0) {
// for(double z1 = -10; z1 < 10; z1 += 2.0) {
//
// for(double x2 = -10; x2 < 10; x2 += 2.0) {
// for(double y2 = -10; y2 < 10; y2 += 2.0) {
// for(double z2 = -10; z2 < 10; z2 += 2.0) {
//
// Values linpoint;
// linpoint.insert(key1, Point3(x1, y1, z1));
// linpoint.insert(key2, Point3(x2, y2, z2));
//
// // Check that the error of the Linearized Jacobian and the original factor match
// // This only works because a BetweenFactor on a Point3 is actually a linear system
// double expected_error = betweenFactor.error(linpoint);
// double actual_error = jacobian.error(linpoint);
// EXPECT_DOUBLES_EQUAL(expected_error, actual_error, 1e-9 );
//
// // Check that the linearized factors are identical
// GaussianFactor::shared_ptr expected_factor = betweenFactor.linearize(linpoint, ordering);
// GaussianFactor::shared_ptr actual_factor = jacobian.linearize(linpoint, ordering);
// CHECK(assert_equal(*expected_factor, *actual_factor));
// }
// }
// }
//
// }
// }
// }
//
//}
/* ************************************************************************* */
TEST( LinearizedFactor, equals_hessian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create two identical factors and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
HessianFactor::shared_ptr hf(new HessianFactor(*jf));
LinearizedHessianFactor hessian1(hf, ordering, values);
LinearizedHessianFactor hessian2(hf, ordering, values);
CHECK(assert_equal(hessian1, hessian2));
}
/* ************************************************************************* */
TEST( LinearizedFactor, clone_hessian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create two identical factors and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
HessianFactor::shared_ptr hf(new HessianFactor(*jf));
LinearizedHessianFactor hessian1(hf, ordering, values);
LinearizedHessianFactor::shared_ptr hessian2 = boost::static_pointer_cast<LinearizedHessianFactor>(hessian1.clone());
CHECK(assert_equal(hessian1, *hessian2));
}
/* ************************************************************************* */
TEST( LinearizedFactor, add_hessian )
{
// Create a Between Factor from a Point3. This is actually a linear factor.
Key x1(1);
Key x2(2);
Ordering ordering;
ordering.push_back(x1);
ordering.push_back(x2);
Values values;
values.insert(x1, Point3(-22.4, +8.5, +2.4));
values.insert(x2, Point3(-21.0, +5.0, +21.0));
Point3 measured(1.0, -2.5, 17.8);
SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
BetweenFactor<Point3> betweenFactor(x1, x2, measured, model);
// Create two identical factors and make sure they're equal
JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
HessianFactor::shared_ptr hf(new HessianFactor(*jf));
LinearizedHessianFactor::shared_ptr hessian(new LinearizedHessianFactor(hf, ordering, values));
NonlinearFactorGraph graph1; graph1.push_back(hessian);
NonlinearFactorGraph graph2; graph2.add(*hessian);
CHECK(assert_equal(graph1, graph2));
}
///* ************************************************************************* */
//TEST( LinearizedFactor, error_hessian )
//{
// // Create a Between Factor from a Point3. This is actually a linear factor.
// Key key1(1);
// Key key2(2);
// Ordering ordering;
// ordering.push_back(key1);
// ordering.push_back(key2);
// Values values;
// values.insert(key1, Point3(-22.4, +8.5, +2.4));
// values.insert(key2, Point3(-21.0, +5.0, +21.0));
//
// Point3 measured(1.0, -2.5, 17.8);
// SharedNoiseModel model = noiseModel::Isotropic::Sigma(3, 0.1);
// BetweenFactor<Point3> betweenFactor(key1, key2, measured, model);
//
//
// // Create a linearized hessian factor
// JacobianFactor::shared_ptr jf = boost::static_pointer_cast<JacobianFactor>(betweenFactor.linearize(values, ordering));
// HessianFactor::shared_ptr hf(new HessianFactor(*jf));
// LinearizedHessianFactor hessian(hf, ordering, values);
//
//
// for(double x1 = -10; x1 < 10; x1 += 2.0) {
// for(double y1 = -10; y1 < 10; y1 += 2.0) {
// for(double z1 = -10; z1 < 10; z1 += 2.0) {
//
// for(double x2 = -10; x2 < 10; x2 += 2.0) {
// for(double y2 = -10; y2 < 10; y2 += 2.0) {
// for(double z2 = -10; z2 < 10; z2 += 2.0) {
//
// Values linpoint;
// linpoint.insert(key1, Point3(x1, y1, z1));
// linpoint.insert(key2, Point3(x2, y2, z2));
//
// // Check that the error of the Linearized Hessian and the original factor match
// // This only works because a BetweenFactor on a Point3 is actually a linear system
// double expected_error = betweenFactor.error(linpoint);
// double actual_error = hessian.error(linpoint);
// EXPECT_DOUBLES_EQUAL(expected_error, actual_error, 1e-9 );
//
// // Check that the linearized factors are identical
// GaussianFactor::shared_ptr expected_factor = HessianFactor::shared_ptr(new HessianFactor(*betweenFactor.linearize(linpoint, ordering)));
// GaussianFactor::shared_ptr actual_factor = hessian.linearize(linpoint, ordering);
// CHECK(assert_equal(*expected_factor, *actual_factor));
// }
// }
// }
//
// }
// }
// }
//
//}
/* ************************************************************************* */
int main()
{
TestResult tr; return TestRegistry::runAllTests(tr);
}
/* ************************************************************************* */