Skip to content

Commit 2d58a5b

Browse files
committed
Importance sampling
1 parent 958a298 commit 2d58a5b

File tree

1 file changed

+117
-51
lines changed

1 file changed

+117
-51
lines changed

gtsam/hybrid/tests/testGaussianMixtureFactor.cpp

+117-51
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
* @date December 2021
1919
*/
2020

21+
#include <gtsam/base/Testable.h>
2122
#include <gtsam/base/TestableAssertions.h>
23+
#include <gtsam/discrete/DiscreteConditional.h>
2224
#include <gtsam/discrete/DiscreteValues.h>
2325
#include <gtsam/hybrid/GaussianMixture.h>
2426
#include <gtsam/hybrid/GaussianMixtureFactor.h>
@@ -33,6 +35,8 @@
3335
// Include for test suite
3436
#include <CppUnitLite/TestHarness.h>
3537

38+
#include <memory>
39+
3640
using namespace std;
3741
using namespace gtsam;
3842
using symbol_shorthand::M;
@@ -387,46 +391,89 @@ namespace test_two_state_estimation {
387391

388392
DiscreteKey m1(M(1), 2);
389393

390-
/// Create Two State Bayes Network with measurements
391-
static HybridBayesNet CreateBayesNet(double mu0, double mu1, double sigma0,
392-
double sigma1,
393-
bool add_second_measurement = false,
394-
double prior_sigma = 1e-3,
395-
double measurement_sigma = 3.0) {
396-
HybridBayesNet hbn;
397-
398-
auto measurement_model = noiseModel::Isotropic::Sigma(1, measurement_sigma);
399-
// Add measurement P(z0 | x0)
400-
auto p_z0 = std::make_shared<GaussianConditional>(
401-
Z(0), Vector1(0.0), -I_1x1, X(0), I_1x1, measurement_model);
402-
hbn.push_back(p_z0);
403-
404-
// Add hybrid motion model
394+
/// Create hybrid motion model p(x1 | x0, m1)
395+
static GaussianMixture::shared_ptr CreateHybridMotionModel(double mu0,
396+
double mu1,
397+
double sigma0,
398+
double sigma1) {
405399
auto model0 = noiseModel::Isotropic::Sigma(1, sigma0);
406400
auto model1 = noiseModel::Isotropic::Sigma(1, sigma1);
407401
auto c0 = make_shared<GaussianConditional>(X(1), Vector1(mu0), I_1x1, X(0),
408402
-I_1x1, model0),
409403
c1 = make_shared<GaussianConditional>(X(1), Vector1(mu1), I_1x1, X(0),
410404
-I_1x1, model1);
411-
412-
auto motion = std::make_shared<GaussianMixture>(
405+
return std::make_shared<GaussianMixture>(
413406
KeyVector{X(1)}, KeyVector{X(0)}, DiscreteKeys{m1}, std::vector{c0, c1});
414-
hbn.push_back(motion);
407+
}
408+
409+
/// Create two state Bayes network with 1 or two measurement models
410+
HybridBayesNet CreateBayesNet(
411+
const GaussianMixture::shared_ptr& hybridMotionModel,
412+
bool add_second_measurement = false) {
413+
HybridBayesNet hbn;
415414

415+
// Add measurement model p(z0 | x0)
416+
const double measurement_sigma = 3.0;
417+
auto measurement_model = noiseModel::Isotropic::Sigma(1, measurement_sigma);
418+
hbn.emplace_shared<GaussianConditional>(Z(0), Vector1(0.0), I_1x1, X(0),
419+
-I_1x1, measurement_model);
420+
421+
// Optionally add second measurement model p(z1 | x1)
416422
if (add_second_measurement) {
417-
// Add second measurement
418-
auto p_z1 = std::make_shared<GaussianConditional>(
419-
Z(1), Vector1(0.0), -I_1x1, X(1), I_1x1, measurement_model);
420-
hbn.push_back(p_z1);
423+
hbn.emplace_shared<GaussianConditional>(Z(1), Vector1(0.0), I_1x1, X(1),
424+
-I_1x1, measurement_model);
421425
}
422426

427+
// Add hybrid motion model
428+
hbn.push_back(hybridMotionModel);
429+
423430
// Discrete uniform prior.
424-
auto p_m1 = std::make_shared<DiscreteConditional>(m1, "0.5/0.5");
425-
hbn.push_back(p_m1);
431+
hbn.emplace_shared<DiscreteConditional>(m1, "0.5/0.5");
426432

427433
return hbn;
428434
}
429435

436+
/// Create importance sampling network p(x1| x0, m1) p(x0) P(m1),
437+
/// using Q(x0) = N(z0, sigma_Q) to sample from p(x0)
438+
HybridBayesNet CreateProposalNet(
439+
const GaussianMixture::shared_ptr& hybridMotionModel, double z0,
440+
double sigma_Q) {
441+
HybridBayesNet hbn;
442+
443+
// Add hybrid motion model
444+
hbn.push_back(hybridMotionModel);
445+
446+
// Add proposal Q(x0) for x0
447+
auto measurement_model = noiseModel::Isotropic::Sigma(1, sigma_Q);
448+
hbn.emplace_shared<GaussianConditional>(
449+
GaussianConditional::FromMeanAndStddev(X(0), Vector1(z0), sigma_Q));
450+
451+
// Discrete uniform prior.
452+
hbn.emplace_shared<DiscreteConditional>(m1, "0.5/0.5");
453+
454+
return hbn;
455+
}
456+
457+
/// Approximate the discrete marginal P(m1) using importance sampling
458+
/// Not typically called as expensive, but values are used in the tests.
459+
void approximateDiscreteMarginal(const HybridBayesNet& hbn,
460+
const HybridBayesNet& proposalNet,
461+
const VectorValues& given) {
462+
// Do importance sampling
463+
double w0 = 0.0, w1 = 0.0;
464+
std::mt19937_64 rng(44);
465+
for (int i = 0; i < 50000; i++) {
466+
HybridValues sample = proposalNet.sample(&rng);
467+
sample.insert(given);
468+
double weight = hbn.evaluate(sample) / proposalNet.evaluate(sample);
469+
(sample.atDiscrete(m1.first) == 0) ? w0 += weight : w1 += weight;
470+
}
471+
double sumWeights = w0 + w1;
472+
double pm1 = w1 / sumWeights;
473+
std::cout << "p(m0) ~ " << 1.0 - pm1 << std::endl;
474+
std::cout << "p(m1) ~ " << pm1 << std::endl;
475+
}
476+
430477
} // namespace test_two_state_estimation
431478

432479
/* ************************************************************************* */
@@ -446,37 +493,48 @@ TEST(GaussianMixtureFactor, TwoStateModel) {
446493
using namespace test_two_state_estimation;
447494

448495
double mu0 = 1.0, mu1 = 3.0;
449-
double sigma = 2.0;
496+
double sigma = 0.5;
497+
auto hybridMotionModel = CreateHybridMotionModel(mu0, mu1, sigma, sigma);
450498

451499
// Start with no measurement on x1, only on x0
452-
HybridBayesNet hbn = CreateBayesNet(mu0, mu1, sigma, sigma, false);
500+
double z0 = 0.5;
453501

454502
VectorValues given;
455-
given.insert(Z(0), Vector1(0.5));
503+
given.insert(Z(0), Vector1(z0));
504+
505+
// Create proposal network for importance sampling
506+
auto proposalNet = CreateProposalNet(hybridMotionModel, z0, 3.0);
507+
EXPECT_LONGS_EQUAL(3, proposalNet.size());
456508

457509
{
510+
HybridBayesNet hbn = CreateBayesNet(hybridMotionModel);
458511
HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given);
459512
HybridBayesNet::shared_ptr bn = gfg.eliminateSequential();
460513

461514
// Since no measurement on x1, we hedge our bets
515+
// Importance sampling run with 50k samples gives 0.49934/0.50066
516+
// approximateDiscreteMarginal(hbn, proposalNet, given);
462517
DiscreteConditional expected(m1, "0.5/0.5");
463518
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete())));
464519
}
465520

466521
{
467522
// Now we add a measurement z1 on x1
468-
hbn = CreateBayesNet(mu0, mu1, sigma, sigma, true);
523+
HybridBayesNet hbn = CreateBayesNet(hybridMotionModel, true);
469524

470-
// If we see z1=2.6 (> 2.5 which is the halfway point),
525+
// If we see z1=4.5 (>> 2.5 which is the halfway point),
471526
// discrete mode should say m1=1
472-
given.insert(Z(1), Vector1(2.6));
527+
const double z1 = 4.5;
528+
given.insert(Z(1), Vector1(z1));
529+
473530
HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given);
474531
HybridBayesNet::shared_ptr bn = gfg.eliminateSequential();
475532

476-
// Since we have a measurement on z2, we get a definite result
477-
DiscreteConditional expected(m1, "0.49772729/0.50227271");
478-
// regression
479-
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 1e-6));
533+
// Since we have a measurement on x1, we get a definite result
534+
// Values taken from an importance sampling run with 50k samples:
535+
// approximateDiscreteMarginal(hbn, proposalNet, given);
536+
DiscreteConditional expected(m1, "0.446629/0.553371");
537+
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 0.01));
480538
}
481539
}
482540

@@ -498,30 +556,32 @@ TEST(GaussianMixtureFactor, TwoStateModel2) {
498556

499557
double mu0 = 1.0, mu1 = 3.0;
500558
double sigma0 = 6.0, sigma1 = 4.0;
501-
auto model0 = noiseModel::Isotropic::Sigma(1, sigma0);
502-
auto model1 = noiseModel::Isotropic::Sigma(1, sigma1);
559+
auto hybridMotionModel = CreateHybridMotionModel(mu0, mu1, sigma0, sigma1);
503560

504561
// Start with no measurement on x1, only on x0
505-
HybridBayesNet hbn = CreateBayesNet(mu0, mu1, sigma0, sigma1, false);
506-
562+
double z0 = 0.5;
507563
VectorValues given;
508-
given.insert(Z(0), Vector1(0.5));
564+
given.insert(Z(0), Vector1(z0));
565+
566+
// Create proposal network for importance sampling
567+
// uncomment this and the approximateDiscreteMarginal calls to run
568+
// auto proposalNet = CreateProposalNet(hybridMotionModel, z0, 3.0);
509569

510570
{
511-
// Start with no measurement on x1, only on x0
571+
HybridBayesNet hbn = CreateBayesNet(hybridMotionModel);
512572
HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given);
513573

514574
{
515575
VectorValues vv{
516-
{X(0), Vector1(0.0)}, {X(1), Vector1(1.0)}, {Z(0), Vector1(0.5)}};
576+
{X(0), Vector1(0.0)}, {X(1), Vector1(1.0)}, {Z(0), Vector1(z0)}};
517577
HybridValues hv0(vv, DiscreteValues{{M(1), 0}}),
518578
hv1(vv, DiscreteValues{{M(1), 1}});
519579
EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0),
520580
gfg.error(hv1) / hbn.error(hv1), 1e-9);
521581
}
522582
{
523583
VectorValues vv{
524-
{X(0), Vector1(0.5)}, {X(1), Vector1(3.0)}, {Z(0), Vector1(0.5)}};
584+
{X(0), Vector1(0.5)}, {X(1), Vector1(3.0)}, {Z(0), Vector1(z0)}};
525585
HybridValues hv0(vv, DiscreteValues{{M(1), 0}}),
526586
hv1(vv, DiscreteValues{{M(1), 1}});
527587
EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0),
@@ -530,6 +590,9 @@ TEST(GaussianMixtureFactor, TwoStateModel2) {
530590

531591
HybridBayesNet::shared_ptr bn = gfg.eliminateSequential();
532592

593+
// Importance sampling run with 50k samples gives 0.49934/0.50066
594+
// approximateDiscreteMarginal(hbn, proposalNet, given);
595+
533596
// Since no measurement on x1, we a 50/50 probability
534597
auto p_m = bn->at(2)->asDiscrete();
535598
EXPECT_DOUBLES_EQUAL(0.5, p_m->operator()(DiscreteValues{{m1.first, 0}}),
@@ -540,16 +603,18 @@ TEST(GaussianMixtureFactor, TwoStateModel2) {
540603

541604
{
542605
// Now we add a measurement z1 on x1
543-
hbn = CreateBayesNet(mu0, mu1, sigma0, sigma1, true);
606+
HybridBayesNet hbn = CreateBayesNet(hybridMotionModel, true);
607+
608+
double z1 = 2.2;
609+
given.insert(Z(1), Vector1(z1));
544610

545-
given.insert(Z(1), Vector1(2.2));
546611
HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given);
547612

548613
{
549614
VectorValues vv{{X(0), Vector1(0.0)},
550615
{X(1), Vector1(1.0)},
551-
{Z(0), Vector1(0.5)},
552-
{Z(1), Vector1(2.2)}};
616+
{Z(0), Vector1(z0)},
617+
{Z(1), Vector1(z1)}};
553618
HybridValues hv0(vv, DiscreteValues{{M(1), 0}}),
554619
hv1(vv, DiscreteValues{{M(1), 1}});
555620
EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0),
@@ -558,8 +623,8 @@ TEST(GaussianMixtureFactor, TwoStateModel2) {
558623
{
559624
VectorValues vv{{X(0), Vector1(0.5)},
560625
{X(1), Vector1(3.0)},
561-
{Z(0), Vector1(0.5)},
562-
{Z(1), Vector1(2.2)}};
626+
{Z(0), Vector1(z0)},
627+
{Z(1), Vector1(z1)}};
563628
HybridValues hv0(vv, DiscreteValues{{M(1), 0}}),
564629
hv1(vv, DiscreteValues{{M(1), 1}});
565630
EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0),
@@ -569,9 +634,10 @@ TEST(GaussianMixtureFactor, TwoStateModel2) {
569634
HybridBayesNet::shared_ptr bn = gfg.eliminateSequential();
570635

571636
// Since we have a measurement on z2, we get a definite result
572-
DiscreteConditional expected(m1, "0.44744586/0.55255414");
573-
// regression
574-
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 1e-6));
637+
// Values taken from an importance sampling run with 50k samples:
638+
// approximateDiscreteMarginal(hbn, proposalNet, given);
639+
DiscreteConditional expected(m1, "0.446345/0.553655");
640+
EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 0.01));
575641
}
576642
}
577643

0 commit comments

Comments
 (0)