Skip to content

Commit

Permalink
Merge pull request #1883 from borglab/feature/hybridISAM
Browse files Browse the repository at this point in the history
Some Hybrid iSAM cleanup
  • Loading branch information
dellaert authored Oct 23, 2024
2 parents 366b514 + 777f0d2 commit 0ffe6c9
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 225 deletions.
8 changes: 8 additions & 0 deletions gtsam/hybrid/HybridGaussianFactorGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ class GTSAM_EXPORT HybridGaussianFactorGraph
explicit HybridGaussianFactorGraph(const CONTAINER& factors)
: Base(factors) {}

/**
* Construct from an initializer lists of GaussianFactor shared pointers.
* Example:
* HybridGaussianFactorGraph graph = { factor1, factor2, factor3 };
*/
HybridGaussianFactorGraph(std::initializer_list<sharedFactor> factors)
: Base(factors) {}

/**
* Implicit copy/downcast constructor to override explicit template container
* constructor. In BayesTree this is used for:
Expand Down
2 changes: 1 addition & 1 deletion gtsam/hybrid/HybridGaussianISAM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* -------------------------------------------------------------------------- */

/**
* @file HybridGaussianISAM.h
* @file HybridGaussianISAM.cpp
* @date March 31, 2022
* @author Fan Jiang
* @author Frank Dellaert
Expand Down
8 changes: 4 additions & 4 deletions gtsam/hybrid/HybridNonlinearISAM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ void HybridNonlinearISAM::update(const HybridNonlinearFactorGraph& newFactors,
if (newFactors.size() > 0) {
// Reorder and relinearize every reorderInterval updates
if (reorderInterval_ > 0 && ++reorderCounter_ >= reorderInterval_) {
// TODO(Varun) Relinearization doesn't take into account pruning
reorder_relinearize();
// TODO(Varun) Re-linearization doesn't take into account pruning
reorderRelinearize();
reorderCounter_ = 0;
}

Expand All @@ -60,7 +60,7 @@ void HybridNonlinearISAM::update(const HybridNonlinearFactorGraph& newFactors,
}

/* ************************************************************************* */
void HybridNonlinearISAM::reorder_relinearize() {
void HybridNonlinearISAM::reorderRelinearize() {
if (factors_.size() > 0) {
// Obtain the new linearization point
const Values newLinPoint = estimate();
Expand All @@ -69,7 +69,7 @@ void HybridNonlinearISAM::reorder_relinearize() {

// Just recreate the whole BayesTree
// TODO: allow for constrained ordering here
// TODO: decouple relinearization and reordering to avoid
// TODO: decouple re-linearization and reordering to avoid
isam_.update(*factors_.linearize(newLinPoint), {}, {},
eliminationFunction_);

Expand Down
10 changes: 5 additions & 5 deletions gtsam/hybrid/HybridNonlinearISAM.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GTSAM_EXPORT HybridNonlinearISAM {
/// The discrete assignment
DiscreteValues assignment_;

/** The original factors, used when relinearizing */
/** The original factors, used when re-linearizing */
HybridNonlinearFactorGraph factors_;

/** The reordering interval and counter */
Expand All @@ -52,8 +52,8 @@ class GTSAM_EXPORT HybridNonlinearISAM {
/// @{

/**
* Periodically reorder and relinearize
* @param reorderInterval is the number of updates between reorderings,
* Periodically reorder and re-linearize
* @param reorderInterval is the number of updates between re-orderings,
* 0 never reorders (and is dangerous for memory consumption)
* 1 (default) reorders every time, in worse case is batch every update
* typical values are 50 or 100
Expand Down Expand Up @@ -124,8 +124,8 @@ class GTSAM_EXPORT HybridNonlinearISAM {
const std::optional<size_t>& maxNrLeaves = {},
const std::optional<Ordering>& ordering = {});

/** Relinearization and reordering of variables */
void reorder_relinearize();
/** Re-linearization and reordering of variables */
void reorderRelinearize();

/// @}
};
Expand Down
40 changes: 27 additions & 13 deletions gtsam/hybrid/tests/Switching.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,21 @@ using MotionModel = BetweenFactor<double>;
// Test fixture with switching network.
/// ϕ(X(0)) .. ϕ(X(k),X(k+1)) .. ϕ(X(k);z_k) .. ϕ(M(0)) .. ϕ(M(K-3),M(K-2))
struct Switching {
private:
HybridNonlinearFactorGraph nonlinearFactorGraph_;

public:
size_t K;
DiscreteKeys modes;
HybridNonlinearFactorGraph nonlinearFactorGraph;
HybridNonlinearFactorGraph unaryFactors, binaryFactors, modeChain;
HybridGaussianFactorGraph linearizedFactorGraph;
Values linearizationPoint;

// Access the flat nonlinear factor graph.
const HybridNonlinearFactorGraph &nonlinearFactorGraph() const {
return nonlinearFactorGraph_;
}

/**
* @brief Create with given number of time steps.
*
Expand All @@ -136,7 +145,7 @@ struct Switching {
*/
Switching(size_t K, double between_sigma = 1.0, double prior_sigma = 0.1,
std::vector<double> measurements = {},
std::string discrete_transition_prob = "1/2 3/2")
std::string transitionProbabilityTable = "1/2 3/2")
: K(K) {
using noiseModel::Isotropic;

Expand All @@ -155,32 +164,36 @@ struct Switching {
// Create hybrid factor graph.

// Add a prior ϕ(X(0)) on X(0).
nonlinearFactorGraph.emplace_shared<PriorFactor<double>>(
nonlinearFactorGraph_.emplace_shared<PriorFactor<double>>(
X(0), measurements.at(0), Isotropic::Sigma(1, prior_sigma));
unaryFactors.push_back(nonlinearFactorGraph_.back());

// Add "motion models" ϕ(X(k),X(k+1),M(k)).
for (size_t k = 0; k < K - 1; k++) {
auto motion_models = motionModels(k, between_sigma);
nonlinearFactorGraph.emplace_shared<HybridNonlinearFactor>(modes[k],
nonlinearFactorGraph_.emplace_shared<HybridNonlinearFactor>(modes[k],
motion_models);
binaryFactors.push_back(nonlinearFactorGraph_.back());
}

// Add measurement factors ϕ(X(k);z_k).
auto measurement_noise = Isotropic::Sigma(1, prior_sigma);
for (size_t k = 1; k < K; k++) {
nonlinearFactorGraph.emplace_shared<PriorFactor<double>>(
nonlinearFactorGraph_.emplace_shared<PriorFactor<double>>(
X(k), measurements.at(k), measurement_noise);
unaryFactors.push_back(nonlinearFactorGraph_.back());
}

// Add "mode chain" ϕ(M(0)) ϕ(M(0),M(1)) ... ϕ(M(K-3),M(K-2))
addModeChain(&nonlinearFactorGraph, discrete_transition_prob);
modeChain = createModeChain(transitionProbabilityTable);
nonlinearFactorGraph_ += modeChain;

// Create the linearization point.
for (size_t k = 0; k < K; k++) {
linearizationPoint.insert<double>(X(k), static_cast<double>(k + 1));
}

linearizedFactorGraph = *nonlinearFactorGraph.linearize(linearizationPoint);
linearizedFactorGraph = *nonlinearFactorGraph_.linearize(linearizationPoint);
}

// Create motion models for a given time step
Expand All @@ -200,15 +213,16 @@ struct Switching {
*
* @param fg The factor graph to which the mode chain is added.
*/
template <typename FACTORGRAPH>
void addModeChain(FACTORGRAPH *fg,
std::string discrete_transition_prob = "1/2 3/2") {
fg->template emplace_shared<DiscreteDistribution>(modes[0], "1/1");
HybridNonlinearFactorGraph createModeChain(
std::string transitionProbabilityTable = "1/2 3/2") {
HybridNonlinearFactorGraph chain;
chain.emplace_shared<DiscreteDistribution>(modes[0], "1/1");
for (size_t k = 0; k < K - 2; k++) {
auto parents = {modes[k]};
fg->template emplace_shared<DiscreteConditional>(
modes[k + 1], parents, discrete_transition_prob);
chain.emplace_shared<DiscreteConditional>(modes[k + 1], parents,
transitionProbabilityTable);
}
return chain;
}
};

Expand Down
53 changes: 25 additions & 28 deletions gtsam/hybrid/tests/testHybridEstimation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
// Include for test suite
#include <CppUnitLite/TestHarness.h>

#include <string>

#include "Switching.h"

using namespace std;
Expand All @@ -55,13 +57,16 @@ std::vector<size_t> discrete_seq = {1, 1, 0, 0, 0, 1, 1, 1, 1, 0,
Switching InitializeEstimationProblem(
const size_t K, const double between_sigma, const double measurement_sigma,
const std::vector<double>& measurements,
const std::string& discrete_transition_prob,
const std::string& transitionProbabilityTable,
HybridNonlinearFactorGraph& graph, Values& initial) {
Switching switching(K, between_sigma, measurement_sigma, measurements,
discrete_transition_prob);
transitionProbabilityTable);

// Add prior on M(0)
graph.push_back(switching.modeChain.at(0));

// Add the X(0) prior
graph.push_back(switching.nonlinearFactorGraph.at(0));
graph.push_back(switching.unaryFactors.at(0));
initial.insert(X(0), switching.linearizationPoint.at<double>(X(0)));

return switching;
Expand Down Expand Up @@ -128,10 +133,9 @@ TEST(HybridEstimation, IncrementalSmoother) {

constexpr size_t maxNrLeaves = 3;
for (size_t k = 1; k < K; k++) {
// Motion Model
graph.push_back(switching.nonlinearFactorGraph.at(k));
// Measurement
graph.push_back(switching.nonlinearFactorGraph.at(k + K - 1));
if (k > 1) graph.push_back(switching.modeChain.at(k - 1)); // Mode chain
graph.push_back(switching.binaryFactors.at(k - 1)); // Motion Model
graph.push_back(switching.unaryFactors.at(k)); // Measurement

initial.insert(X(k), switching.linearizationPoint.at<double>(X(k)));

Expand Down Expand Up @@ -176,10 +180,9 @@ TEST(HybridEstimation, ValidPruningError) {

constexpr size_t maxNrLeaves = 3;
for (size_t k = 1; k < K; k++) {
// Motion Model
graph.push_back(switching.nonlinearFactorGraph.at(k));
// Measurement
graph.push_back(switching.nonlinearFactorGraph.at(k + K - 1));
if (k > 1) graph.push_back(switching.modeChain.at(k - 1)); // Mode chain
graph.push_back(switching.binaryFactors.at(k - 1)); // Motion Model
graph.push_back(switching.unaryFactors.at(k)); // Measurement

initial.insert(X(k), switching.linearizationPoint.at<double>(X(k)));

Expand Down Expand Up @@ -225,15 +228,17 @@ TEST(HybridEstimation, ISAM) {

HybridGaussianFactorGraph linearized;

const size_t maxNrLeaves = 3;
for (size_t k = 1; k < K; k++) {
// Motion Model
graph.push_back(switching.nonlinearFactorGraph.at(k));
// Measurement
graph.push_back(switching.nonlinearFactorGraph.at(k + K - 1));
if (k > 1) graph.push_back(switching.modeChain.at(k - 1)); // Mode chain
graph.push_back(switching.binaryFactors.at(k - 1)); // Motion Model
graph.push_back(switching.unaryFactors.at(k)); // Measurement

initial.insert(X(k), switching.linearizationPoint.at<double>(X(k)));

isam.update(graph, initial, 3);
isam.update(graph, initial, maxNrLeaves);
// isam.saveGraph("NLiSAM" + std::to_string(k) + ".dot");
// GTSAM_PRINT(isam);

graph.resize(0);
initial.clear();
Expand Down Expand Up @@ -339,12 +344,8 @@ TEST(HybridEstimation, Probability) {
HybridValues hybrid_values = bayesNet->optimize();

// This is the correct sequence as designed
DiscreteValues discrete_seq;
discrete_seq[M(0)] = 1;
discrete_seq[M(1)] = 1;
discrete_seq[M(2)] = 0;

EXPECT(assert_equal(discrete_seq, hybrid_values.discrete()));
DiscreteValues expectedSequence{{M(0), 1}, {M(1), 1}, {M(2), 0}};
EXPECT(assert_equal(expectedSequence, hybrid_values.discrete()));
}

/****************************************************************************/
Expand Down Expand Up @@ -411,12 +412,8 @@ TEST(HybridEstimation, ProbabilityMultifrontal) {
HybridValues hybrid_values = discreteBayesTree->optimize();

// This is the correct sequence as designed
DiscreteValues discrete_seq;
discrete_seq[M(0)] = 1;
discrete_seq[M(1)] = 1;
discrete_seq[M(2)] = 0;

EXPECT(assert_equal(discrete_seq, hybrid_values.discrete()));
DiscreteValues expectedSequence{{M(0), 1}, {M(1), 1}, {M(2), 0}};
EXPECT(assert_equal(expectedSequence, hybrid_values.discrete()));
}

/*********************************************************************************
Expand Down
Loading

0 comments on commit 0ffe6c9

Please sign in to comment.