Skip to content

Commit 276747c

Browse files
authored
Merge pull request #1805 from borglab/direct-hybrid-fg
2 parents 63c4e33 + 67a8b8f commit 276747c

11 files changed

+410
-84
lines changed

gtsam/discrete/DiscreteBayesNet.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
#include <gtsam/discrete/DiscreteBayesNet.h>
2020
#include <gtsam/discrete/DiscreteConditional.h>
21-
#include <gtsam/discrete/DiscreteFactorGraph.h>
22-
#include <gtsam/discrete/DiscreteLookupDAG.h>
2321
#include <gtsam/inference/FactorGraph-inst.h>
2422

2523
namespace gtsam {

gtsam/hybrid/HybridGaussianConditional.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,7 @@ std::shared_ptr<HybridGaussianFactor> HybridGaussianConditional::likelihood(
222222
} else {
223223
// Add a constant to the likelihood in case the noise models
224224
// are not all equal.
225-
double c = 2.0 * Cgm_Kgcm;
226-
return {likelihood_m, c};
225+
return {likelihood_m, Cgm_Kgcm};
227226
}
228227
});
229228
return std::make_shared<HybridGaussianFactor>(
@@ -232,8 +231,7 @@ std::shared_ptr<HybridGaussianFactor> HybridGaussianConditional::likelihood(
232231

233232
/* ************************************************************************* */
234233
std::set<DiscreteKey> DiscreteKeysAsSet(const DiscreteKeys &discreteKeys) {
235-
std::set<DiscreteKey> s;
236-
s.insert(discreteKeys.begin(), discreteKeys.end());
234+
std::set<DiscreteKey> s(discreteKeys.begin(), discreteKeys.end());
237235
return s;
238236
}
239237

gtsam/hybrid/HybridGaussianFactor.cpp

+14-11
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ namespace gtsam {
3030

3131
/**
3232
* @brief Helper function to augment the [A|b] matrices in the factor components
33-
* with the normalizer values.
34-
* This is done by storing the normalizer value in
33+
* with the additional scalar values.
34+
* This is done by storing the value in
3535
* the `b` vector as an additional row.
3636
*
3737
* @param factors DecisionTree of GaussianFactors and arbitrary scalars.
@@ -46,31 +46,34 @@ HybridGaussianFactor::Factors augment(
4646
AlgebraicDecisionTree<Key> valueTree;
4747
std::tie(gaussianFactors, valueTree) = unzip(factors);
4848

49-
// Normalize
49+
// Compute minimum value for normalization.
5050
double min_value = valueTree.min();
51-
AlgebraicDecisionTree<Key> values =
52-
valueTree.apply([&min_value](double n) { return n - min_value; });
5351

5452
// Finally, update the [A|b] matrices.
55-
auto update = [&values](const Assignment<Key> &assignment,
56-
const HybridGaussianFactor::sharedFactor &gf) {
53+
auto update = [&min_value](const GaussianFactorValuePair &gfv) {
54+
auto [gf, value] = gfv;
55+
5756
auto jf = std::dynamic_pointer_cast<JacobianFactor>(gf);
5857
if (!jf) return gf;
59-
// If the log_normalizer is 0, do nothing
60-
if (values(assignment) == 0.0) return gf;
58+
59+
double normalized_value = value - min_value;
60+
61+
// If the value is 0, do nothing
62+
if (normalized_value == 0.0) return gf;
6163

6264
GaussianFactorGraph gfg;
6365
gfg.push_back(jf);
6466

6567
Vector c(1);
66-
c << std::sqrt(values(assignment));
68+
// When hiding c inside the `b` vector, value == 0.5*c^2
69+
c << std::sqrt(2.0 * normalized_value);
6770
auto constantFactor = std::make_shared<JacobianFactor>(c);
6871

6972
gfg.push_back(constantFactor);
7073
return std::dynamic_pointer_cast<GaussianFactor>(
7174
std::make_shared<JacobianFactor>(gfg));
7275
};
73-
return gaussianFactors.apply(update);
76+
return HybridGaussianFactor::Factors(factors, update);
7477
}
7578

7679
/* *******************************************************************************/

gtsam/hybrid/HybridGaussianFactor.h

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ using GaussianFactorValuePair = std::pair<GaussianFactor::shared_ptr, double>;
4545
* where the set of discrete variables indexes to
4646
* the continuous gaussian distribution.
4747
*
48+
* In factor graphs the error function typically returns 0.5*|A*x - b|^2, i.e.,
49+
* the negative log-likelihood for a Gaussian noise model.
50+
* In hybrid factor graphs we allow *adding* an arbitrary scalar dependent on
51+
* the discrete assignment.
52+
* For example, adding a 70/30 mode probability is supported by providing the
53+
* scalars $-log(.7)$ and $-log(.3)$.
54+
* Note that adding a common constant will not make any difference in the
55+
* optimization, so $-log(70)$ and $-log(30)$ work just as well.
56+
*
4857
* @ingroup hybrid
4958
*/
5059
class GTSAM_EXPORT HybridGaussianFactor : public HybridFactor {

gtsam/hybrid/HybridNonlinearFactor.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ using NonlinearFactorValuePair = std::pair<NonlinearFactor::shared_ptr, double>;
4545
* This class stores all factors as HybridFactors which can then be typecast to
4646
* one of (NonlinearFactor, GaussianFactor) which can then be checked to perform
4747
* the correct operation.
48+
*
49+
* In factor graphs the error function typically returns 0.5*|h(x)-z|^2, i.e.,
50+
* the negative log-likelihood for a Gaussian noise model.
51+
* In hybrid factor graphs we allow *adding* an arbitrary scalar dependent on
52+
* the discrete assignment.
53+
* For example, adding a 70/30 mode probability is supported by providing the
54+
* scalars $-log(.7)$ and $-log(.3)$.
55+
* Note that adding a common constant will not make any difference in the
56+
* optimization, so $-log(70)$ and $-log(30)$ work just as well.
57+
*
58+
* @ingroup hybrid
4859
*/
4960
class HybridNonlinearFactor : public HybridFactor {
5061
public:
@@ -134,7 +145,7 @@ class HybridNonlinearFactor : public HybridFactor {
134145
auto errorFunc =
135146
[continuousValues](const std::pair<sharedFactor, double>& f) {
136147
auto [factor, val] = f;
137-
return factor->error(continuousValues) + (0.5 * val);
148+
return factor->error(continuousValues) + val;
138149
};
139150
DecisionTree<Key, double> result(factors_, errorFunc);
140151
return result;
@@ -153,7 +164,7 @@ class HybridNonlinearFactor : public HybridFactor {
153164
auto [factor, val] = factors_(discreteValues);
154165
// Compute the error for the selected factor
155166
const double factorError = factor->error(continuousValues);
156-
return factorError + (0.5 * val);
167+
return factorError + val;
157168
}
158169

159170
/**

gtsam/hybrid/HybridValues.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ HybridValues::HybridValues(const VectorValues& cv, const DiscreteValues& dv,
3636
void HybridValues::print(const std::string& s,
3737
const KeyFormatter& keyFormatter) const {
3838
std::cout << s << ": \n";
39-
continuous_.print(" Continuous",
40-
keyFormatter); // print continuous components
41-
discrete_.print(" Discrete", keyFormatter); // print discrete components
39+
// print continuous components
40+
continuous_.print(" Continuous", keyFormatter);
41+
// print discrete components
42+
discrete_.print(" Discrete", keyFormatter);
43+
// print nonlinear components
44+
nonlinear_.print(" Nonlinear", keyFormatter);
4245
}
4346

4447
/* ************************************************************************* */

gtsam/hybrid/tests/testHybridGaussianFactor.cpp

+170-5
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ namespace test_two_state_estimation {
396396

397397
DiscreteKey m1(M(1), 2);
398398

399-
void addMeasurement(HybridBayesNet& hbn, Key z_key, Key x_key, double sigma) {
399+
void addMeasurement(HybridBayesNet &hbn, Key z_key, Key x_key, double sigma) {
400400
auto measurement_model = noiseModel::Isotropic::Sigma(1, sigma);
401401
hbn.emplace_shared<GaussianConditional>(z_key, Vector1(0.0), I_1x1, x_key,
402402
-I_1x1, measurement_model);
@@ -420,7 +420,7 @@ static HybridGaussianConditional::shared_ptr CreateHybridMotionModel(
420420

421421
/// Create two state Bayes network with 1 or two measurement models
422422
HybridBayesNet CreateBayesNet(
423-
const HybridGaussianConditional::shared_ptr& hybridMotionModel,
423+
const HybridGaussianConditional::shared_ptr &hybridMotionModel,
424424
bool add_second_measurement = false) {
425425
HybridBayesNet hbn;
426426

@@ -443,9 +443,9 @@ HybridBayesNet CreateBayesNet(
443443

444444
/// Approximate the discrete marginal P(m1) using importance sampling
445445
std::pair<double, double> approximateDiscreteMarginal(
446-
const HybridBayesNet& hbn,
447-
const HybridGaussianConditional::shared_ptr& hybridMotionModel,
448-
const VectorValues& given, size_t N = 100000) {
446+
const HybridBayesNet &hbn,
447+
const HybridGaussianConditional::shared_ptr &hybridMotionModel,
448+
const VectorValues &given, size_t N = 100000) {
449449
/// Create importance sampling network q(x0,x1,m) = p(x1|x0,m1) q(x0) P(m1),
450450
/// using q(x0) = N(z0, sigmaQ) to sample x0.
451451
HybridBayesNet q;
@@ -741,6 +741,171 @@ TEST(HybridGaussianFactor, TwoStateModel4) {
741741
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 0.002));
742742
}
743743

744+
namespace test_direct_factor_graph {
745+
/**
746+
* @brief Create a Factor Graph by directly specifying all
747+
* the factors instead of creating conditionals first.
748+
* This way we can directly provide the likelihoods and
749+
* then perform linearization.
750+
*
751+
* @param values Initial values to linearize around.
752+
* @param means The means of the HybridGaussianFactor components.
753+
* @param sigmas The covariances of the HybridGaussianFactor components.
754+
* @param m1 The discrete key.
755+
* @return HybridGaussianFactorGraph
756+
*/
757+
static HybridGaussianFactorGraph CreateFactorGraph(
758+
const gtsam::Values &values, const std::vector<double> &means,
759+
const std::vector<double> &sigmas, DiscreteKey &m1,
760+
double measurement_noise = 1e-3) {
761+
auto model0 = noiseModel::Isotropic::Sigma(1, sigmas[0]);
762+
auto model1 = noiseModel::Isotropic::Sigma(1, sigmas[1]);
763+
auto prior_noise = noiseModel::Isotropic::Sigma(1, measurement_noise);
764+
765+
auto f0 =
766+
std::make_shared<BetweenFactor<double>>(X(0), X(1), means[0], model0)
767+
->linearize(values);
768+
auto f1 =
769+
std::make_shared<BetweenFactor<double>>(X(0), X(1), means[1], model1)
770+
->linearize(values);
771+
772+
// Create HybridGaussianFactor
773+
// We take negative since we want
774+
// the underlying scalar to be log(\sqrt(|2πΣ|))
775+
std::vector<GaussianFactorValuePair> factors{
776+
{f0, -model0->logNormalizationConstant()},
777+
{f1, -model1->logNormalizationConstant()}};
778+
HybridGaussianFactor motionFactor({X(0), X(1)}, m1, factors);
779+
780+
HybridGaussianFactorGraph hfg;
781+
hfg.push_back(motionFactor);
782+
783+
hfg.push_back(PriorFactor<double>(X(0), values.at<double>(X(0)), prior_noise)
784+
.linearize(values));
785+
786+
return hfg;
787+
}
788+
} // namespace test_direct_factor_graph
789+
790+
/* ************************************************************************* */
791+
/**
792+
* @brief Test components with differing means but the same covariances.
793+
* The factor graph is
794+
* *-X1-*-X2
795+
* |
796+
* M1
797+
*/
798+
TEST(HybridGaussianFactor, DifferentMeansFG) {
799+
using namespace test_direct_factor_graph;
800+
801+
DiscreteKey m1(M(1), 2);
802+
803+
Values values;
804+
double x1 = 0.0, x2 = 1.75;
805+
values.insert(X(0), x1);
806+
values.insert(X(1), x2);
807+
808+
std::vector<double> means = {0.0, 2.0}, sigmas = {1e-0, 1e-0};
809+
810+
HybridGaussianFactorGraph hfg = CreateFactorGraph(values, means, sigmas, m1);
811+
812+
{
813+
auto bn = hfg.eliminateSequential();
814+
HybridValues actual = bn->optimize();
815+
816+
HybridValues expected(
817+
VectorValues{{X(0), Vector1(0.0)}, {X(1), Vector1(-1.75)}},
818+
DiscreteValues{{M(1), 0}});
819+
820+
EXPECT(assert_equal(expected, actual));
821+
822+
DiscreteValues dv0{{M(1), 0}};
823+
VectorValues cont0 = bn->optimize(dv0);
824+
double error0 = bn->error(HybridValues(cont0, dv0));
825+
// regression
826+
EXPECT_DOUBLES_EQUAL(0.69314718056, error0, 1e-9);
827+
828+
DiscreteValues dv1{{M(1), 1}};
829+
VectorValues cont1 = bn->optimize(dv1);
830+
double error1 = bn->error(HybridValues(cont1, dv1));
831+
EXPECT_DOUBLES_EQUAL(error0, error1, 1e-9);
832+
}
833+
834+
{
835+
auto prior_noise = noiseModel::Isotropic::Sigma(1, 1e-3);
836+
hfg.push_back(
837+
PriorFactor<double>(X(1), means[1], prior_noise).linearize(values));
838+
839+
auto bn = hfg.eliminateSequential();
840+
HybridValues actual = bn->optimize();
841+
842+
HybridValues expected(
843+
VectorValues{{X(0), Vector1(0.0)}, {X(1), Vector1(0.25)}},
844+
DiscreteValues{{M(1), 1}});
845+
846+
EXPECT(assert_equal(expected, actual));
847+
848+
{
849+
DiscreteValues dv{{M(1), 0}};
850+
VectorValues cont = bn->optimize(dv);
851+
double error = bn->error(HybridValues(cont, dv));
852+
// regression
853+
EXPECT_DOUBLES_EQUAL(2.12692448787, error, 1e-9);
854+
}
855+
{
856+
DiscreteValues dv{{M(1), 1}};
857+
VectorValues cont = bn->optimize(dv);
858+
double error = bn->error(HybridValues(cont, dv));
859+
// regression
860+
EXPECT_DOUBLES_EQUAL(0.126928487854, error, 1e-9);
861+
}
862+
}
863+
}
864+
865+
/* ************************************************************************* */
866+
/**
867+
* @brief Test components with differing covariances but the same means.
868+
* The factor graph is
869+
* *-X1-*-X2
870+
* |
871+
* M1
872+
*/
873+
TEST(HybridGaussianFactor, DifferentCovariancesFG) {
874+
using namespace test_direct_factor_graph;
875+
876+
DiscreteKey m1(M(1), 2);
877+
878+
Values values;
879+
double x1 = 1.0, x2 = 1.0;
880+
values.insert(X(0), x1);
881+
values.insert(X(1), x2);
882+
883+
std::vector<double> means = {0.0, 0.0}, sigmas = {1e2, 1e-2};
884+
885+
// Create FG with HybridGaussianFactor and prior on X1
886+
HybridGaussianFactorGraph fg = CreateFactorGraph(values, means, sigmas, m1);
887+
auto hbn = fg.eliminateSequential();
888+
889+
VectorValues cv;
890+
cv.insert(X(0), Vector1(0.0));
891+
cv.insert(X(1), Vector1(0.0));
892+
893+
// Check that the error values at the MLE point μ.
894+
AlgebraicDecisionTree<Key> errorTree = hbn->errorTree(cv);
895+
896+
DiscreteValues dv0{{M(1), 0}};
897+
DiscreteValues dv1{{M(1), 1}};
898+
899+
// regression
900+
EXPECT_DOUBLES_EQUAL(9.90348755254, errorTree(dv0), 1e-9);
901+
EXPECT_DOUBLES_EQUAL(0.69314718056, errorTree(dv1), 1e-9);
902+
903+
DiscreteConditional expected_m1(m1, "0.5/0.5");
904+
DiscreteConditional actual_m1 = *(hbn->at(2)->asDiscrete());
905+
906+
EXPECT(assert_equal(expected_m1, actual_m1));
907+
}
908+
744909
/* ************************************************************************* */
745910
int main() {
746911
TestResult tr;

0 commit comments

Comments
 (0)