Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nonlinear Hybrid #1263

Merged
merged 42 commits into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2927d92
add HybridNonlinearFactor and nonlinear HybridFactorGraph
varunagrawal May 28, 2022
78ea90b
Add MixtureFactor for nonlinear factor types
varunagrawal May 28, 2022
e91a354
convert to cpp
varunagrawal May 29, 2022
9cbd2ef
Base Hybrid Factor Graph
varunagrawal May 29, 2022
01b9a65
make GaussianMixtureFactor a subclass of HybridGaussianFactor
varunagrawal May 29, 2022
fe0d666
HybridFactorGraph fixes
varunagrawal May 29, 2022
cdd030b
Make MixtureFactor only work with NonlinearFactors and make some impr…
varunagrawal May 29, 2022
08fab8a
HybridNonlinearFactor linearize method
varunagrawal May 29, 2022
9279bd6
push_back for GaussianHybridFactor
varunagrawal May 29, 2022
3274cb1
clean up testHybridFactorGraph, need to add more tests
varunagrawal May 29, 2022
6c36b2c
GaussianHybridFactorGraph inherits from HybridFactorGraph
varunagrawal May 29, 2022
85f4b48
Improvements to GaussianHybridFactorGraph, make MixtureFactor a subcl…
varunagrawal May 30, 2022
53e8c32
Add NonlinearHybridFactorGraph class
varunagrawal May 30, 2022
7e18277
fix base class
varunagrawal May 30, 2022
119679a
linearize returns object instead of pointer
varunagrawal May 30, 2022
3212dde
remove unneeded method
varunagrawal May 30, 2022
0c16799
GaussianMixtureFactor inherits from HybridFactor
varunagrawal May 30, 2022
e711a62
More tests working
varunagrawal May 30, 2022
3780b8c
Merge branch 'develop' into feature/nonlinear-hybrid
varunagrawal Jul 26, 2022
8ddc2ea
rename to HybridNonlinearFactorGraph
varunagrawal Jul 26, 2022
7a55341
add IsGaussian template check
varunagrawal Jul 26, 2022
43c28e7
renaming fixes
varunagrawal Jul 26, 2022
987448f
remove derived push_back in HybridNonlinearFactorGraph and HybridFact…
varunagrawal Jul 26, 2022
8471c97
add nonlinear switching system tests
varunagrawal Jul 30, 2022
8907922
get more nonlinear tests to work and make some updates
varunagrawal Aug 1, 2022
16124f3
get all but 2 tests passing
varunagrawal Aug 2, 2022
ee124c3
fix discrete only elimination (use EliminateForMPE)
varunagrawal Aug 2, 2022
b39c231
all tests pass!!!
varunagrawal Aug 2, 2022
0f732d7
fix discrete conditional test
varunagrawal Aug 3, 2022
6670779
Wrap DiscreteLookupTable
varunagrawal Aug 3, 2022
92a5868
Merge branch 'develop' into feature/nonlinear-hybrid
varunagrawal Aug 3, 2022
db56909
Merge branch 'hybrid/pruning' into feature/nonlinear-hybrid
varunagrawal Aug 3, 2022
5965d8f
change discrete key variable from C to M
varunagrawal Aug 8, 2022
f5e046f
split HybridNonlinearFactorGraph to .h and .cpp
varunagrawal Aug 8, 2022
51d2f07
fix printing and key bug in MixtureFactor linearize
varunagrawal Aug 8, 2022
a3eacaa
fix adding priors in Switching
varunagrawal Aug 8, 2022
588f56e
HybridGaussianISAM unit tests
varunagrawal Aug 8, 2022
60c88e3
fix print tests
varunagrawal Aug 10, 2022
4ea897c
cleaner printing
varunagrawal Aug 10, 2022
fbceda3
got some more tests working
varunagrawal Aug 10, 2022
0e4db30
use templetized constructor for MixtureFactor
varunagrawal Aug 10, 2022
2a974a4
Address review comments
varunagrawal Aug 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add MixtureFactor for nonlinear factor types
  • Loading branch information
varunagrawal committed May 28, 2022
commit 78ea90bb27ea338f445100e7eabce390675747a3
7 changes: 1 addition & 6 deletions gtsam/hybrid/HybridNonlinearFactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,9 @@ namespace gtsam {
HybridNonlinearFactor::HybridNonlinearFactor(NonlinearFactor::shared_ptr other)
: Base(other->keys()), inner_(other) {}

/* ************************************************************************* */
HybridNonlinearFactor::HybridNonlinearFactor(NonlinearFactor &&nf)
: Base(nf.keys()),
inner_(boost::make_shared<NonlinearFactor>(std::move(nf))) {}

/* ************************************************************************* */
bool HybridNonlinearFactor::equals(const HybridFactor &lf, double tol) const {
return Base(lf, tol);
return Base::equals(lf, tol);
}

/* ************************************************************************* */
Expand Down
3 changes: 0 additions & 3 deletions gtsam/hybrid/HybridNonlinearFactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ class HybridNonlinearFactor : public HybridFactor {
// Explicit conversion from a shared ptr of GF
explicit HybridNonlinearFactor(NonlinearFactor::shared_ptr other);

// Forwarding constructor from concrete NonlinearFactor
explicit HybridNonlinearFactor(NonlinearFactor &&jf);

public:
/// @name Testable
/// @{
Expand Down
240 changes: 240 additions & 0 deletions gtsam/hybrid/MixtureFactor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/* ----------------------------------------------------------------------------
* Copyright 2020 The Ambitious Folks of the MRG
* See LICENSE for the license information
* -------------------------------------------------------------------------- */

/**
* @file MixtureFactor.h
* @brief Nonlinear Mixture factor of continuous and discrete.
* @author Kevin Doherty, kdoherty@mit.edu
* @author Varun Agrawal
* @date December 2021
*/

#pragma once

#include <gtsam/discrete/DiscreteValues.h>
#include <gtsam/hybrid/GaussianMixtureFactor.h>
#include <gtsam/hybrid/HybridFactor.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
#include <gtsam/nonlinear/Symbol.h>

#include <algorithm>
#include <boost/format.hpp>
#include <cmath>
#include <limits>
#include <vector>

namespace gtsam {

/**
* @brief Implementation of a discrete conditional mixture factor. Implements a
* joint discrete-continuous factor where the discrete variable serves to
* "select" a mixture component corresponding to a NonlinearFactor type
* of measurement.
*/
template <class NonlinearFactorType>
class MixtureFactor : public HybridFactor {
public:
using Base = HybridFactor;
using This = MixtureFactor;
using shared_ptr = boost::shared_ptr<MixtureFactor>;
using sharedFactor = boost::shared_ptr<NonlinearFactorType>;

/// typedef for DecisionTree which has Keys as node labels and
/// NonlinearFactorType as leaf nodes.
using Factors = DecisionTree<Key, sharedFactor>;

private:
/// Decision tree of Gaussian factors indexed by discrete keys.
Factors factors_;
bool normalized_;

public:
MixtureFactor() = default;

/**
* @brief Construct from Decision tree.
*
* @param keys Vector of keys for continuous factors.
* @param discreteKeys Vector of discrete keys.
* @param factors Decision tree with of shared factors.
* @param normalized Flag indicating if the factor error is already
* normalized.
*/
MixtureFactor(const KeyVector& keys, const DiscreteKeys& discreteKeys,
const Factors& factors, bool normalized = false)
: Base(keys, discreteKeys), factors_(factors), normalized_(normalized) {}

/**
* @brief Convenience constructor that generates the underlying factor
* decision tree for us.
*
* Here it is important that the vector of factors has the correct number of
* elements based on the number of discrete keys and the cardinality of the
* keys, so that the decision tree is constructed appropriately.
*
* @param keys Vector of keys for continuous factors.
* @param discreteKeys Vector of discrete keys.
* @param factors Vector of shared pointers to factors.
* @param normalized Flag indicating if the factor error is already
* normalized.
*/
MixtureFactor(const KeyVector& keys, const DiscreteKeys& discreteKeys,
const std::vector<sharedFactor>& factors,
bool normalized = false)
: MixtureFactor(keys, discreteKeys, Factors(discreteKeys, factors),
normalized) {}

~MixtureFactor() = default;

/**
* @brief Compute error of factor given both continuous and discrete values.
*
* @param continuousVals The continuous Values.
* @param discreteVals The discrete Values.
* @return double The error of this factor.
*/
double error(const Values& continuousVals,
const DiscreteValues& discreteVals) const {
// Retrieve the factor corresponding to the assignment in discreteVals.
auto factor = factors_(discreteVals);
// Compute the error for the selected factor
const double factorError = factor->error(continuousVals);
if (normalized_) return factorError;
return factorError +
this->nonlinearFactorLogNormalizingConstant(*factor, continuousVals);
}

size_t dim() const {
// TODO(Varun)
throw std::runtime_error("MixtureFactor::dim not implemented.");
}

/// Testable
/// @{

/// print to stdout
void print(
const std::string& s = "MixtureFactor",
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override {
std::cout << (s.empty() ? "" : s + " ");
std::cout << "(";
auto contKeys = keys();
auto dKeys = discreteKeys();
for (DiscreteKey key : dKeys) {
auto it = std::find(contKeys.begin(), contKeys.end(), key.first);
contKeys.erase(it);
}
for (Key key : contKeys) {
std::cout << " " << keyFormatter(key);
}
std::cout << ";";
for (DiscreteKey key : dKeys) {
std::cout << " " << keyFormatter(key.first);
}
std::cout << " ) \n";
auto valueFormatter = [](const sharedFactor& v) {
if (v) {
return (boost::format("Nonlinear factor on %d keys") % v->size()).str();
} else {
return std::string("nullptr");
}
};
factors_.print("", keyFormatter, valueFormatter);
}

/// Check equality
bool equals(const HybridFactor& other, double tol = 1e-9) const override {
// We attempt a dynamic cast from HybridFactor to MixtureFactor. If it
// fails, return false.
if (!dynamic_cast<const MixtureFactor*>(&other)) return false;

// If the cast is successful, we'll properly construct a MixtureFactor
// object from `other`
const MixtureFactor& f(static_cast<const MixtureFactor&>(other));

// Ensure that this MixtureFactor and `f` have the same `factors_`.
auto compare = [tol](const sharedFactor& a, const sharedFactor& b) {
return traits<NonlinearFactorType>::Equals(*a, *b, tol);
};
if (!factors_.equals(f.factors_, compare)) return false;

// If everything above passes, and the keys_, discreteKeys_ and normalized_
// member variables are identical, return true.
return (std::equal(keys_.begin(), keys_.end(), f.keys().begin()) &&
(discreteKeys_ == f.discreteKeys_) &&
(normalized_ == f.normalized_));
}

/// @}

/// Linearize specific nonlinear factors based on the assignment in
/// discreteValues.
GaussianFactor::shared_ptr linearize(
const Values& continuousVals, const DiscreteValues& discreteVals) const {
auto factor = factors_(discreteVals);
return factor->linearize(continuousVals);
}

/// Linearize all the continuous factors to get a GaussianMixtureFactor.
boost::shared_ptr<GaussianMixtureFactor> linearize(
const Values& continuousVals) const {
// functional to linearize each factor in the decision tree
auto linearizeDT = [continuousVals](const sharedFactor& factor) {
return factor->linearize(continuousVals);
};

DecisionTree<Key, GaussianFactor::shared_ptr> linearized_factors(
factors_, linearizeDT);

return boost::make_shared<GaussianMixtureFactor>(keys_, discreteKeys_,
linearized_factors);
}

/**
* If the component factors are not already normalized, we want to compute
* their normalizing constants so that the resulting joint distribution is
* appropriately computed. Remember, this is the _negative_ normalizing
* constant for the measurement likelihood (since we are minimizing the
* _negative_ log-likelihood).
*/
double nonlinearFactorLogNormalizingConstant(
const NonlinearFactorType& factor, const Values& values) const {
// Information matrix (inverse covariance matrix) for the factor.
Matrix infoMat;

// NOTE: This is sloppy (and mallocs!), is there a cleaner way?
auto factorPtr = boost::make_shared<NonlinearFactorType>(factor);

// If this is a NoiseModelFactor, we'll use its noiseModel to
// otherwise noiseModelFactor will be nullptr
auto noiseModelFactor =
boost::dynamic_pointer_cast<NoiseModelFactor>(factorPtr);
if (noiseModelFactor) {
// If dynamic cast to NoiseModelFactor succeeded, see if the noise model
// is Gaussian
auto noiseModel = noiseModelFactor->noiseModel();

auto gaussianNoiseModel =
boost::dynamic_pointer_cast<noiseModel::Gaussian>(noiseModel);
if (gaussianNoiseModel) {
// If the noise model is Gaussian, retrieve the information matrix
infoMat = gaussianNoiseModel->information();
} else {
// If the factor is not a Gaussian factor, we'll linearize it to get
// something with a normalized noise model
// TODO(kevin): does this make sense to do? I think maybe not in
// general? Should we just yell at the user?
auto gaussianFactor = factor.linearize(values);
infoMat = gaussianFactor->information();
}
}

// Compute the (negative) log of the normalizing constant
return -(factor.dim() * log(2.0 * M_PI) / 2.0) -
(log(infoMat.determinant()) / 2.0);
}
};

} // namespace gtsam