From 6b9a4969b9075e50862fca1d1d1ad105fedddff9 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 13:59:33 -0500 Subject: [PATCH 01/19] Fix some typos --- gtsam/discrete/DiscreteConditional.h | 5 +---- gtsam/discrete/tests/testDiscreteConditional.cpp | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index a8000f4867..e8cf6afe07 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -62,8 +62,6 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, * conditional probability table (CPT) in 00 01 10 11 order. For * three-valued, it would be 00 01 02 10 11 12 20 21 22, etc.... * - * The first string is parsed to add a key and parents. - * * Example: DiscreteConditional P(D, {B,E}, table); */ DiscreteConditional(const DiscreteKey& key, const DiscreteKeys& parents, @@ -75,8 +73,7 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, * probability table (CPT) in 00 01 10 11 order. For three-valued, it would * be 00 01 02 10 11 12 20 21 22, etc.... * - * The first string is parsed to add a key and parents. The second string - * parses into a table. + * The string is parsed into a Signature::Table. * * Example: DiscreteConditional P(D, {B,E}, "9/1 2/8 3/7 1/9"); */ diff --git a/gtsam/discrete/tests/testDiscreteConditional.cpp b/gtsam/discrete/tests/testDiscreteConditional.cpp index f42792e71d..74092d17ca 100644 --- a/gtsam/discrete/tests/testDiscreteConditional.cpp +++ b/gtsam/discrete/tests/testDiscreteConditional.cpp @@ -10,10 +10,11 @@ * -------------------------------------------------------------------------- */ /* - * @file testDecisionTreeFactor.cpp + * @file testDiscreteConditional.cpp * @brief unit tests for DiscreteConditional * @author Duy-Nguyen Ta - * @date Feb 14, 2011 + * @author Frank dellaert + * @date Feb 14, 2011 */ #include From 268a49ec1cbdb441a53b2da6acd175f9c68c8730 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 13:59:48 -0500 Subject: [PATCH 02/19] DiscretePrior class --- gtsam/discrete/DiscretePrior.h | 87 ++++++++++++++++++++++ gtsam/discrete/tests/testDiscretePrior.cpp | 40 ++++++++++ 2 files changed, 127 insertions(+) create mode 100644 gtsam/discrete/DiscretePrior.h create mode 100644 gtsam/discrete/tests/testDiscretePrior.cpp diff --git a/gtsam/discrete/DiscretePrior.h b/gtsam/discrete/DiscretePrior.h new file mode 100644 index 0000000000..f38c78ca1c --- /dev/null +++ b/gtsam/discrete/DiscretePrior.h @@ -0,0 +1,87 @@ +/* ---------------------------------------------------------------------------- + + * 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 DiscretePrior.h + * @date December 2021 + * @author Frank Dellaert + */ + +#pragma once + +#include + +#include + +namespace gtsam { + +/** + * A prior probability on a set of discrete variables. + * Derives from DiscreteConditional + */ +class GTSAM_EXPORT DiscretePrior : public DiscreteConditional { + public: + using Base = DiscreteConditional; + + /// @name Standard Constructors + /// @{ + + /// Default constructor needed for serialization. + DiscretePrior() {} + + /// Constructor from factor. + DiscretePrior(const DecisionTreeFactor& f) : Base(f.size(), f) {} + + /** + * Construct from a Signature. + * + * Example: DiscretePrior P(D % "3/2"); + */ + DiscretePrior(const Signature& s) : Base(s) {} + + /** + * Construct from key and a Signature::Table specifying the + * conditional probability table (CPT). + * + * Example: DiscretePrior P(D, table); + */ + DiscretePrior(const DiscreteKey& key, const Signature::Table& table) + : Base(Signature(key, {}, table)) {} + + /** + * Construct from key and a string specifying the conditional + * probability table (CPT). + * + * Example: DiscretePrior P(D, "9/1 2/8 3/7 1/9"); + */ + DiscretePrior(const DiscreteKey& key, const std::string& spec) + : DiscretePrior(Signature(key, {}, spec)) {} + + /// @} + /// @name Testable + /// @{ + + /// GTSAM-style print + void print( + const std::string& s = "Discrete Prior: ", + const KeyFormatter& formatter = DefaultKeyFormatter) const override { + Base::print(s, formatter); + } + + /// @} +}; +// DiscretePrior + +// traits +template <> +struct traits : public Testable {}; + +} // namespace gtsam diff --git a/gtsam/discrete/tests/testDiscretePrior.cpp b/gtsam/discrete/tests/testDiscretePrior.cpp new file mode 100644 index 0000000000..f63b8af0b1 --- /dev/null +++ b/gtsam/discrete/tests/testDiscretePrior.cpp @@ -0,0 +1,40 @@ +/* ---------------------------------------------------------------------------- + + * 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 testDiscretePrior.cpp + * @brief unit tests for DiscretePrior + * @author Frank dellaert + * @date December 2021 + */ + +#include +#include +#include + +using namespace std; +using namespace gtsam; + +/* ************************************************************************* */ +TEST(DiscretePrior, constructors) { + DiscreteKey X(0, 2); + DiscretePrior actual(X % "2/3"); + DecisionTreeFactor f(X, "0.4 0.6"); + DiscretePrior expected(f); + EXPECT(assert_equal(expected, actual, 1e-9)); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ From 4727783304a8e6fda0b6493f8aa9cf9ea7871f2c Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 14:11:46 -0500 Subject: [PATCH 03/19] Wrap DiscretePrior --- gtsam/discrete/discrete.i | 10 +++++ python/gtsam/tests/test_DiscretePrior.py | 49 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 python/gtsam/tests/test_DiscretePrior.py diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index f0dc72a24d..44eece2257 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -71,6 +71,16 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { gtsam::DefaultKeyFormatter) const; }; +#include +virtual class DiscretePrior : gtsam::DiscreteConditional { + DiscretePrior(); + DiscretePrior(const gtsam::DecisionTreeFactor& f); + DiscretePrior(const gtsam::DiscreteKey& key, string spec); + void print(string s = "Discrete Prior\n", + const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; +}; + #include class DiscreteBayesNet { DiscreteBayesNet(); diff --git a/python/gtsam/tests/test_DiscretePrior.py b/python/gtsam/tests/test_DiscretePrior.py new file mode 100644 index 0000000000..e95b05135d --- /dev/null +++ b/python/gtsam/tests/test_DiscretePrior.py @@ -0,0 +1,49 @@ +""" +GTSAM Copyright 2010-2021, Georgia Tech Research Corporation, +Atlanta, Georgia 30332-0415 +All Rights Reserved + +See LICENSE for the license information + +Unit tests for Discrete Priors. +Author: Varun Agrawal +""" + +# pylint: disable=no-name-in-module, invalid-name + +import unittest + +from gtsam import DiscretePrior, DecisionTreeFactor, DiscreteKeys +from gtsam.utils.test_case import GtsamTestCase + + +class TestDiscretePrior(GtsamTestCase): + """Tests for Discrete Priors.""" + + def test_constructor(self): + """Test various constructors.""" + X = 0, 2 + actual = DiscretePrior(X, "2/3") + keys = DiscreteKeys() + keys.push_back(X) + f = DecisionTreeFactor(keys, "0.4 0.6") + expected = DiscretePrior(f) + self.gtsamAssertEquals(actual, expected) + + def test_markdown(self): + """Test the _repr_markdown_ method.""" + + X = 0, 2 + prior = DiscretePrior(X, "2/3") + expected = " $P(0)$:\n" \ + "|0|value|\n" \ + "|:-:|:-:|\n" \ + "|0|0.4|\n" \ + "|1|0.6|\n" \ + + actual = prior._repr_markdown_() + self.assertEqual(actual, expected) + + +if __name__ == "__main__": + unittest.main() From 4bc7b0ba8542b39bda34b6c8260519495da91294 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 15:21:02 -0500 Subject: [PATCH 04/19] single argument variants --- gtsam/discrete/DiscretePrior.h | 27 ++++++++++++++++++++++ gtsam/discrete/tests/testDiscretePrior.cpp | 17 +++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/gtsam/discrete/DiscretePrior.h b/gtsam/discrete/DiscretePrior.h index f38c78ca1c..96a0b6f3a5 100644 --- a/gtsam/discrete/DiscretePrior.h +++ b/gtsam/discrete/DiscretePrior.h @@ -75,7 +75,34 @@ class GTSAM_EXPORT DiscretePrior : public DiscreteConditional { const KeyFormatter& formatter = DefaultKeyFormatter) const override { Base::print(s, formatter); } + /// @} + /// @name Standard interface + /// @{ + /// Evaluate given a single value. + double operator()(size_t value) const { + if (nrFrontals() != 1) + throw std::invalid_argument( + "Single value operator can only be invoked on single-variable " + "priors"); + DiscreteValues values; + values.emplace(keys_[0], value); + return Base::operator()(values); + } + + /// Evaluate given a single value. + std::vector pmf() const { + if (nrFrontals() != 1) + throw std::invalid_argument( + "DiscretePrior::pmf only defined for single-variable priors"); + const size_t nrValues = cardinalities_.at(keys_[0]); + std::vector array; + array.reserve(nrValues); + for (size_t v = 0; v < nrValues; v++) { + array.push_back(operator()(v)); + } + return array; + } /// @} }; // DiscretePrior diff --git a/gtsam/discrete/tests/testDiscretePrior.cpp b/gtsam/discrete/tests/testDiscretePrior.cpp index f63b8af0b1..b91926cc05 100644 --- a/gtsam/discrete/tests/testDiscretePrior.cpp +++ b/gtsam/discrete/tests/testDiscretePrior.cpp @@ -23,15 +23,30 @@ using namespace std; using namespace gtsam; +static const DiscreteKey X(0, 2); + /* ************************************************************************* */ TEST(DiscretePrior, constructors) { - DiscreteKey X(0, 2); DiscretePrior actual(X % "2/3"); DecisionTreeFactor f(X, "0.4 0.6"); DiscretePrior expected(f); EXPECT(assert_equal(expected, actual, 1e-9)); } +/* ************************************************************************* */ +TEST(DiscretePrior, operator) { + DiscretePrior prior(X % "2/3"); + EXPECT_DOUBLES_EQUAL(prior(0), 0.4, 1e-9); + EXPECT_DOUBLES_EQUAL(prior(1), 0.6, 1e-9); +} + +/* ************************************************************************* */ +TEST(DiscretePrior, to_vector) { + DiscretePrior prior(X % "2/3"); + vector expected {0.4, 0.6}; + EXPECT(prior.pmf() == expected); +} + /* ************************************************************************* */ int main() { TestResult tr; From 10628a0ddc8acf9502286493abef2f92219cadef Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 15:22:57 -0500 Subject: [PATCH 05/19] cpp file --- gtsam/discrete/DiscretePrior.cpp | 50 ++++++++++++++++++++++++++++++++ gtsam/discrete/DiscretePrior.h | 29 ++++-------------- 2 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 gtsam/discrete/DiscretePrior.cpp diff --git a/gtsam/discrete/DiscretePrior.cpp b/gtsam/discrete/DiscretePrior.cpp new file mode 100644 index 0000000000..3941e0199e --- /dev/null +++ b/gtsam/discrete/DiscretePrior.cpp @@ -0,0 +1,50 @@ +/* ---------------------------------------------------------------------------- + + * 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 DiscretePrior.cpp + * @date December 2021 + * @author Frank Dellaert + */ + +#include + +namespace gtsam { + +void DiscretePrior::print(const std::string& s, + const KeyFormatter& formatter) const { + Base::print(s, formatter); +} + +double DiscretePrior::operator()(size_t value) const { + if (nrFrontals() != 1) + throw std::invalid_argument( + "Single value operator can only be invoked on single-variable " + "priors"); + DiscreteValues values; + values.emplace(keys_[0], value); + return Base::operator()(values); +} + +std::vector DiscretePrior::pmf() const { + if (nrFrontals() != 1) + throw std::invalid_argument( + "DiscretePrior::pmf only defined for single-variable priors"); + const size_t nrValues = cardinalities_.at(keys_[0]); + std::vector array; + array.reserve(nrValues); + for (size_t v = 0; v < nrValues; v++) { + array.push_back(operator()(v)); + } + return array; +} + +} // namespace gtsam diff --git a/gtsam/discrete/DiscretePrior.h b/gtsam/discrete/DiscretePrior.h index 96a0b6f3a5..f44df4987b 100644 --- a/gtsam/discrete/DiscretePrior.h +++ b/gtsam/discrete/DiscretePrior.h @@ -72,37 +72,18 @@ class GTSAM_EXPORT DiscretePrior : public DiscreteConditional { /// GTSAM-style print void print( const std::string& s = "Discrete Prior: ", - const KeyFormatter& formatter = DefaultKeyFormatter) const override { - Base::print(s, formatter); - } + const KeyFormatter& formatter = DefaultKeyFormatter) const override; + /// @} /// @name Standard interface /// @{ /// Evaluate given a single value. - double operator()(size_t value) const { - if (nrFrontals() != 1) - throw std::invalid_argument( - "Single value operator can only be invoked on single-variable " - "priors"); - DiscreteValues values; - values.emplace(keys_[0], value); - return Base::operator()(values); - } + double operator()(size_t value) const; /// Evaluate given a single value. - std::vector pmf() const { - if (nrFrontals() != 1) - throw std::invalid_argument( - "DiscretePrior::pmf only defined for single-variable priors"); - const size_t nrValues = cardinalities_.at(keys_[0]); - std::vector array; - array.reserve(nrValues); - for (size_t v = 0; v < nrValues; v++) { - array.push_back(operator()(v)); - } - return array; - } + std::vector pmf() const; + /// @} }; // DiscretePrior From a1b8f52da85aedb1eb2a063726a29829f8ad2be7 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 15:25:33 -0500 Subject: [PATCH 06/19] Wrap single-argument methods --- gtsam/discrete/discrete.i | 2 ++ python/gtsam/tests/test_DiscretePrior.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 44eece2257..9782480c3d 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -79,6 +79,8 @@ virtual class DiscretePrior : gtsam::DiscreteConditional { void print(string s = "Discrete Prior\n", const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; + double operator()(size_t value) const; + std::vector pmf() const; }; #include diff --git a/python/gtsam/tests/test_DiscretePrior.py b/python/gtsam/tests/test_DiscretePrior.py index e95b05135d..2b277ae918 100644 --- a/python/gtsam/tests/test_DiscretePrior.py +++ b/python/gtsam/tests/test_DiscretePrior.py @@ -13,16 +13,18 @@ import unittest -from gtsam import DiscretePrior, DecisionTreeFactor, DiscreteKeys +import numpy as np +from gtsam import DecisionTreeFactor, DiscreteKeys, DiscretePrior from gtsam.utils.test_case import GtsamTestCase +X = 0, 2 + class TestDiscretePrior(GtsamTestCase): """Tests for Discrete Priors.""" def test_constructor(self): """Test various constructors.""" - X = 0, 2 actual = DiscretePrior(X, "2/3") keys = DiscreteKeys() keys.push_back(X) @@ -30,10 +32,19 @@ def test_constructor(self): expected = DiscretePrior(f) self.gtsamAssertEquals(actual, expected) + def test_operator(self): + prior = DiscretePrior(X, "2/3") + self.assertAlmostEqual(prior(0), 0.4) + self.assertAlmostEqual(prior(1), 0.6) + + def test_pmf(self): + prior = DiscretePrior(X, "2/3") + expected = np.array([0.4, 0.6]) + np.testing.assert_allclose(expected, prior.pmf()) + def test_markdown(self): """Test the _repr_markdown_ method.""" - X = 0, 2 prior = DiscretePrior(X, "2/3") expected = " $P(0)$:\n" \ "|0|value|\n" \ From 3339517340ee0b4252c949a66073447f805ca92d Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 16:05:05 -0500 Subject: [PATCH 07/19] Additional DiscreteConditional constructors to support wrapper. --- gtsam/discrete/DiscreteBayesNet.h | 6 +++++ gtsam/discrete/DiscreteConditional.h | 14 ++++++++++ gtsam/discrete/discrete.i | 21 +++++++++------ gtsam/discrete/tests/testDiscreteBayesNet.cpp | 4 +-- python/gtsam/tests/test_DiscreteBayesNet.py | 26 +++++++------------ 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/gtsam/discrete/DiscreteBayesNet.h b/gtsam/discrete/DiscreteBayesNet.h index d78eed08f2..aed4cec0aa 100644 --- a/gtsam/discrete/DiscreteBayesNet.h +++ b/gtsam/discrete/DiscreteBayesNet.h @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace gtsam { @@ -75,6 +76,11 @@ namespace gtsam { // Add inherited versions of add. using Base::add; + /** Add a DiscretePrior using a table or a string */ + void add(const DiscreteKey& key, const std::string& spec) { + emplace_shared(key, spec); + } + /** Add a DiscreteCondtional */ template void add(Args&&... args) { diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index e8cf6afe07..e95dfb5157 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -81,6 +81,20 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, const std::string& spec) : DiscreteConditional(Signature(key, parents, spec)) {} + /// No-parent specialization; can also use DiscretePrior. + DiscreteConditional(const DiscreteKey& key, const std::string& spec) + : DiscreteConditional(Signature(key, {}, spec)) {} + + /// Single-parent specialization + DiscreteConditional(const DiscreteKey& key, const std::string& spec, + const DiscreteKey& parent1) + : DiscreteConditional(Signature(key, {parent1}, spec)) {} + + /// Two-parent specialization + DiscreteConditional(const DiscreteKey& key, const std::string& spec, + const DiscreteKey& parent1, const DiscreteKey& parent2) + : DiscreteConditional(Signature(key, {parent1, parent2}, spec)) {} + /** construct P(X|Y)=P(X,Y)/P(Y) from P(X,Y) and P(Y) */ DiscreteConditional(const DecisionTreeFactor& joint, const DecisionTreeFactor& marginal); diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 9782480c3d..f2e7456d83 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -84,10 +84,17 @@ virtual class DiscretePrior : gtsam::DiscreteConditional { }; #include -class DiscreteBayesNet { +class DiscreteBayesNet { DiscreteBayesNet(); - void add(const gtsam::DiscreteKey& key, - const gtsam::DiscreteKeys& parents, string spec); + void add(const gtsam::DiscreteConditional& s); + void add(const gtsam::DiscreteKey& key, string spec); + void add(const gtsam::DiscreteKey& key, string spec, + const gtsam::DiscreteKey& parent1); + void add(const gtsam::DiscreteKey& key, string spec, + const gtsam::DiscreteKey& parent1, + const gtsam::DiscreteKey& parent2); + void add(const gtsam::DiscreteKey& key, const gtsam::DiscreteKeys& parents, + string spec); bool empty() const; size_t size() const; gtsam::KeySet keys() const; @@ -98,15 +105,13 @@ class DiscreteBayesNet { bool equals(const gtsam::DiscreteBayesNet& other, double tol = 1e-9) const; string dot(const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; - void saveGraph(string s, - const gtsam::KeyFormatter& keyFormatter = - gtsam::DefaultKeyFormatter) const; - void add(const gtsam::DiscreteConditional& s); + void saveGraph(string s, const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; double operator()(const gtsam::DiscreteValues& values) const; gtsam::DiscreteValues optimize() const; gtsam::DiscreteValues sample() const; string markdown(const gtsam::KeyFormatter& keyFormatter = - gtsam::DefaultKeyFormatter) const; + gtsam::DefaultKeyFormatter) const; }; #include diff --git a/gtsam/discrete/tests/testDiscreteBayesNet.cpp b/gtsam/discrete/tests/testDiscreteBayesNet.cpp index ea58165669..7a5f180ad7 100644 --- a/gtsam/discrete/tests/testDiscreteBayesNet.cpp +++ b/gtsam/discrete/tests/testDiscreteBayesNet.cpp @@ -75,8 +75,8 @@ TEST(DiscreteBayesNet, bayesNet) { TEST(DiscreteBayesNet, Asia) { DiscreteBayesNet asia; - asia.add(Asia % "99/1"); - asia.add(Smoking % "50/50"); + asia.add(Asia, "99/1"); + asia.add(Smoking % "50/50"); // Signature version asia.add(Tuberculosis | Asia = "99/1 95/5"); asia.add(LungCancer | Smoking = "99/1 90/10"); diff --git a/python/gtsam/tests/test_DiscreteBayesNet.py b/python/gtsam/tests/test_DiscreteBayesNet.py index bf09da1935..706cdf93d0 100644 --- a/python/gtsam/tests/test_DiscreteBayesNet.py +++ b/python/gtsam/tests/test_DiscreteBayesNet.py @@ -14,7 +14,7 @@ import unittest from gtsam import (DiscreteBayesNet, DiscreteConditional, DiscreteFactorGraph, - DiscreteKeys, DiscreteValues, Ordering) + DiscreteKeys, DiscretePrior, DiscreteValues, Ordering) from gtsam.utils.test_case import GtsamTestCase @@ -53,24 +53,18 @@ def test_Asia(self): XRay = (2, 2) Dyspnea = (1, 2) - def P(keys): - dks = DiscreteKeys() - for key in keys: - dks.push_back(key) - return dks - asia = DiscreteBayesNet() - asia.add(Asia, P([]), "99/1") - asia.add(Smoking, P([]), "50/50") + asia.add(Asia, "99/1") + asia.add(Smoking, "50/50") - asia.add(Tuberculosis, P([Asia]), "99/1 95/5") - asia.add(LungCancer, P([Smoking]), "99/1 90/10") - asia.add(Bronchitis, P([Smoking]), "70/30 40/60") + asia.add(Tuberculosis, "99/1 95/5", Asia) + asia.add(LungCancer, "99/1 90/10", Smoking) + asia.add(Bronchitis, "70/30 40/60", Smoking) - asia.add(Either, P([Tuberculosis, LungCancer]), "F T T T") + asia.add(Either, "F T T T", Tuberculosis, LungCancer) - asia.add(XRay, P([Either]), "95/5 2/98") - asia.add(Dyspnea, P([Either, Bronchitis]), "9/1 2/8 3/7 1/9") + asia.add(XRay, "95/5 2/98", Either) + asia.add(Dyspnea, "9/1 2/8 3/7 1/9", Either, Bronchitis) # Convert to factor graph fg = DiscreteFactorGraph(asia) @@ -80,7 +74,7 @@ def P(keys): for j in range(8): ordering.push_back(j) chordal = fg.eliminateSequential(ordering) - expected2 = DiscreteConditional(Bronchitis, P([]), "11/9") + expected2 = DiscretePrior(Bronchitis, "11/9") self.gtsamAssertEquals(chordal.at(7), expected2) # solve From 075a7cd0fdf8936d1e4726dc2d78b98d7524587a Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 17:02:18 -0500 Subject: [PATCH 08/19] markdown that renders better on github/pages --- gtsam/discrete/DiscreteConditional.cpp | 6 +++--- gtsam/discrete/tests/testDiscreteBayesNet.cpp | 4 ++-- gtsam/discrete/tests/testDiscreteConditional.cpp | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gtsam/discrete/DiscreteConditional.cpp b/gtsam/discrete/DiscreteConditional.cpp index 080dbba9bc..2d3a5ddf8b 100644 --- a/gtsam/discrete/DiscreteConditional.cpp +++ b/gtsam/discrete/DiscreteConditional.cpp @@ -228,7 +228,7 @@ std::string DiscreteConditional::markdown( std::stringstream ss; // Print out signature. - ss << " $P("; + ss << " *P("; bool first = true; for (Key key : frontals()) { if (!first) ss << ","; @@ -237,7 +237,7 @@ std::string DiscreteConditional::markdown( } if (nrParents() == 0) { // We have no parents, call factor method. - ss << ")$:" << std::endl; + ss << ")*:\n" << std::endl; ss << DecisionTreeFactor::markdown(keyFormatter); return ss.str(); } @@ -250,7 +250,7 @@ std::string DiscreteConditional::markdown( ss << keyFormatter(parent); first = false; } - ss << ")$:" << std::endl; + ss << ")*:\n" << std::endl; // Print out header and construct argument for `cartesianProduct`. std::vector> pairs; diff --git a/gtsam/discrete/tests/testDiscreteBayesNet.cpp b/gtsam/discrete/tests/testDiscreteBayesNet.cpp index 7a5f180ad7..1de45905a6 100644 --- a/gtsam/discrete/tests/testDiscreteBayesNet.cpp +++ b/gtsam/discrete/tests/testDiscreteBayesNet.cpp @@ -180,13 +180,13 @@ TEST(DiscreteBayesNet, markdown) { string expected = "`DiscreteBayesNet` of size 2\n" "\n" - " $P(Asia)$:\n" + " *P(Asia)*:\n\n" "|Asia|value|\n" "|:-:|:-:|\n" "|0|0.99|\n" "|1|0.01|\n" "\n" - " $P(Smoking|Asia)$:\n" + " *P(Smoking|Asia)*:\n\n" "|Asia|0|1|\n" "|:-:|:-:|:-:|\n" "|0|0.8|0.2|\n" diff --git a/gtsam/discrete/tests/testDiscreteConditional.cpp b/gtsam/discrete/tests/testDiscreteConditional.cpp index 74092d17ca..604b0ce718 100644 --- a/gtsam/discrete/tests/testDiscreteConditional.cpp +++ b/gtsam/discrete/tests/testDiscreteConditional.cpp @@ -114,7 +114,7 @@ TEST(DiscreteConditional, markdown_prior) { DiscreteKey A(Symbol('x', 1), 3); DiscreteConditional conditional(A % "1/2/2"); string expected = - " $P(x1)$:\n" + " *P(x1)*:\n\n" "|x1|value|\n" "|:-:|:-:|\n" "|0|0.2|\n" @@ -131,7 +131,7 @@ TEST(DiscreteConditional, markdown_multivalued) { DiscreteConditional conditional( A | B = "2/88/10 2/20/78 33/33/34 33/33/34 95/2/3"); string expected = - " $P(a1|b1)$:\n" + " *P(a1|b1)*:\n\n" "|b1|0|1|2|\n" "|:-:|:-:|:-:|:-:|\n" "|0|0.02|0.88|0.1|\n" @@ -149,7 +149,7 @@ TEST(DiscreteConditional, markdown) { DiscreteKey A(2, 2), B(1, 2), C(0, 3); DiscreteConditional conditional(A, {B, C}, "0/1 1/3 1/1 3/1 0/1 1/0"); string expected = - " $P(A|B,C)$:\n" + " *P(A|B,C)*:\n\n" "|B|C|0|1|\n" "|:-:|:-:|:-:|:-:|\n" "|0|0|0|1|\n" From 1d12995be50f820f870bb0398fdb16b460789bbc Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 18:23:48 -0500 Subject: [PATCH 09/19] Add no-argument solve and sample to DiscretePrior --- gtsam/discrete/DiscretePrior.h | 18 +++++++++++++++++- gtsam/discrete/discrete.i | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/gtsam/discrete/DiscretePrior.h b/gtsam/discrete/DiscretePrior.h index f44df4987b..1a7c6ae6cb 100644 --- a/gtsam/discrete/DiscretePrior.h +++ b/gtsam/discrete/DiscretePrior.h @@ -81,9 +81,25 @@ class GTSAM_EXPORT DiscretePrior : public DiscreteConditional { /// Evaluate given a single value. double operator()(size_t value) const; - /// Evaluate given a single value. + /// We also want to keep the Base version, taking DiscreteValues: + // TODO(dellaert): does not play well with wrapper! + // using Base::operator(); + + /// Return entire probability mass function. std::vector pmf() const; + /** + * solve a conditional + * @return MPE value of the child (1 frontal variable). + */ + size_t solve() const { return Base::solve({}); } + + /** + * sample + * @return sample from conditional + */ + size_t sample() const { return Base::sample({}); } + /// @} }; // DiscretePrior diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index f2e7456d83..9bb05085b1 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -81,6 +81,8 @@ virtual class DiscretePrior : gtsam::DiscreteConditional { gtsam::DefaultKeyFormatter) const; double operator()(size_t value) const; std::vector pmf() const; + size_t solve() const; + size_t sample() const; }; #include From 5882847604fbcd5b3c35ef58ffff1bab07caeb80 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 23:14:22 -0500 Subject: [PATCH 10/19] Specialized DecisionTreeFactor constructors --- gtsam/discrete/DecisionTreeFactor.h | 17 +++++++++++++++++ gtsam/discrete/DiscreteKey.h | 4 +--- gtsam/discrete/discrete.i | 25 +++++++++++++++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/gtsam/discrete/DecisionTreeFactor.h b/gtsam/discrete/DecisionTreeFactor.h index 27ee67cf23..43dd892fc4 100644 --- a/gtsam/discrete/DecisionTreeFactor.h +++ b/gtsam/discrete/DecisionTreeFactor.h @@ -61,6 +61,23 @@ namespace gtsam { DiscreteFactor(keys.indices()), Potentials(keys, table) { } + /// Single-key specialization + template + DecisionTreeFactor(const DiscreteKey& key, SOURCE table) + : DecisionTreeFactor(DiscreteKeys{key}, table) {} + + /// Two-key specialization + template + DecisionTreeFactor(const DiscreteKey& key1, const DiscreteKey& key2, + SOURCE table) + : DecisionTreeFactor({key1, key2}, table) {} + + /// Three-key specialization + template + DecisionTreeFactor(const DiscreteKey& key1, const DiscreteKey& key2, + const DiscreteKey& key3, SOURCE table) + : DecisionTreeFactor({key1, key2, key3}, table) {} + /** Construct from a DiscreteConditional type */ DecisionTreeFactor(const DiscreteConditional& c); diff --git a/gtsam/discrete/DiscreteKey.h b/gtsam/discrete/DiscreteKey.h index 86f1bcf63d..ae4dac38fc 100644 --- a/gtsam/discrete/DiscreteKey.h +++ b/gtsam/discrete/DiscreteKey.h @@ -43,9 +43,7 @@ namespace gtsam { DiscreteKeys() : std::vector::vector() {} /// Construct from a key - DiscreteKeys(const DiscreteKey& key) { - push_back(key); - } + explicit DiscreteKeys(const DiscreteKey& key) { push_back(key); } /// Construct from a vector of keys DiscreteKeys(const std::vector& keys) : diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 9bb05085b1..0f319562f5 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -30,9 +30,15 @@ class DiscreteFactor { }; #include -virtual class DecisionTreeFactor: gtsam::DiscreteFactor { +virtual class DecisionTreeFactor : gtsam::DiscreteFactor { DecisionTreeFactor(); DecisionTreeFactor(const gtsam::DiscreteKeys& keys, string table); + DecisionTreeFactor(const gtsam::DiscreteKey& key, const std::string& spec); + DecisionTreeFactor(const gtsam::DiscreteKey& key1, + const gtsam::DiscreteKey& key2, const std::string& spec); + DecisionTreeFactor(const gtsam::DiscreteKey& key1, + const gtsam::DiscreteKey& key2, + const gtsam::DiscreteKey& key3, const std::string& spec); DecisionTreeFactor(const gtsam::DiscreteConditional& c); void print(string s = "DecisionTreeFactor\n", const gtsam::KeyFormatter& keyFormatter = @@ -40,13 +46,19 @@ virtual class DecisionTreeFactor: gtsam::DiscreteFactor { bool equals(const gtsam::DecisionTreeFactor& other, double tol = 1e-9) const; string dot(bool showZero = false) const; string markdown(const gtsam::KeyFormatter& keyFormatter = - gtsam::DefaultKeyFormatter) const; + gtsam::DefaultKeyFormatter) const; }; #include virtual class DiscreteConditional : gtsam::DecisionTreeFactor { DiscreteConditional(); DiscreteConditional(size_t nFrontals, const gtsam::DecisionTreeFactor& f); + DiscreteConditional(const gtsam::DiscreteKey& key, string spec); + DiscreteConditional(const gtsam::DiscreteKey& key, string spec, + const gtsam::DiscreteKey& parent1); + DiscreteConditional(const gtsam::DiscreteKey& key, string spec, + const gtsam::DiscreteKey& parent1, + const gtsam::DiscreteKey& parent2); DiscreteConditional(const gtsam::DiscreteKey& key, const gtsam::DiscreteKeys& parents, string spec); DiscreteConditional(const gtsam::DecisionTreeFactor& joint, @@ -62,13 +74,14 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { string s = "Discrete Conditional: ", const gtsam::KeyFormatter& formatter = gtsam::DefaultKeyFormatter) const; gtsam::DecisionTreeFactor* toFactor() const; - gtsam::DecisionTreeFactor* chooseAsFactor(const gtsam::DiscreteValues& parentsValues) const; + gtsam::DecisionTreeFactor* chooseAsFactor( + const gtsam::DiscreteValues& parentsValues) const; size_t solve(const gtsam::DiscreteValues& parentsValues) const; size_t sample(const gtsam::DiscreteValues& parentsValues) const; - void solveInPlace(gtsam::DiscreteValues@ parentsValues) const; - void sampleInPlace(gtsam::DiscreteValues@ parentsValues) const; + void solveInPlace(gtsam::DiscreteValues @parentsValues) const; + void sampleInPlace(gtsam::DiscreteValues @parentsValues) const; string markdown(const gtsam::KeyFormatter& keyFormatter = - gtsam::DefaultKeyFormatter) const; + gtsam::DefaultKeyFormatter) const; }; #include From 34c3d6af5eb822918eff0e50f2b4f38e811d4c91 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 23:14:35 -0500 Subject: [PATCH 11/19] Replaced add variants with single variadic template --- gtsam/discrete/DiscreteFactorGraph.h | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/gtsam/discrete/DiscreteFactorGraph.h b/gtsam/discrete/DiscreteFactorGraph.h index 38091829fb..6856493f7f 100644 --- a/gtsam/discrete/DiscreteFactorGraph.h +++ b/gtsam/discrete/DiscreteFactorGraph.h @@ -101,29 +101,12 @@ public EliminateableFactorGraph { /// @} - // Add single key decision-tree factor. - template - void add(const DiscreteKey& j, SOURCE table) { - DiscreteKeys keys; - keys.push_back(j); - emplace_shared(keys, table); + /** Add a decision-tree factor */ + template + void add(Args&&... args) { + emplace_shared(std::forward(args)...); } - - // Add binary key decision-tree factor. - template - void add(const DiscreteKey& j1, const DiscreteKey& j2, SOURCE table) { - DiscreteKeys keys; - keys.push_back(j1); - keys.push_back(j2); - emplace_shared(keys, table); - } - - // Add shared discreteFactor immediately from arguments. - template - void add(const DiscreteKeys& keys, SOURCE table) { - emplace_shared(keys, table); - } - + /** Return the set of variables involved in the factors (set union) */ KeySet keys() const; From dbe5c0fa81fd097e90b4c40fe2e7c02d97146bcc Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 26 Dec 2021 23:42:12 -0500 Subject: [PATCH 12/19] Allow a vector of doubles for single-variable factors --- gtsam/discrete/DecisionTreeFactor.h | 4 ++++ gtsam/discrete/discrete.i | 3 +++ gtsam/discrete/tests/testDecisionTreeFactor.cpp | 2 +- python/gtsam/tests/test_DiscreteFactorGraph.py | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/gtsam/discrete/DecisionTreeFactor.h b/gtsam/discrete/DecisionTreeFactor.h index 43dd892fc4..b5a0371197 100644 --- a/gtsam/discrete/DecisionTreeFactor.h +++ b/gtsam/discrete/DecisionTreeFactor.h @@ -66,6 +66,10 @@ namespace gtsam { DecisionTreeFactor(const DiscreteKey& key, SOURCE table) : DecisionTreeFactor(DiscreteKeys{key}, table) {} + /// Single-key specialization, with vector of doubles. + DecisionTreeFactor(const DiscreteKey& key, const std::vector& row) + : DecisionTreeFactor(DiscreteKeys{key}, row) {} + /// Two-key specialization template DecisionTreeFactor(const DiscreteKey& key1, const DiscreteKey& key2, diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 0f319562f5..971250ba13 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -33,6 +33,8 @@ class DiscreteFactor { virtual class DecisionTreeFactor : gtsam::DiscreteFactor { DecisionTreeFactor(); DecisionTreeFactor(const gtsam::DiscreteKeys& keys, string table); + DecisionTreeFactor(const gtsam::DiscreteKey& key, + const std::vector& spec); DecisionTreeFactor(const gtsam::DiscreteKey& key, const std::string& spec); DecisionTreeFactor(const gtsam::DiscreteKey& key1, const gtsam::DiscreteKey& key2, const std::string& spec); @@ -175,6 +177,7 @@ class DiscreteFactorGraph { DiscreteFactorGraph(); DiscreteFactorGraph(const gtsam::DiscreteBayesNet& bayesNet); + void add(const gtsam::DiscreteKey& j, const std::vector& spec); void add(const gtsam::DiscreteKey& j, string table); void add(const gtsam::DiscreteKey& j1, const gtsam::DiscreteKey& j2, string table); void add(const gtsam::DiscreteKeys& keys, string table); diff --git a/gtsam/discrete/tests/testDecisionTreeFactor.cpp b/gtsam/discrete/tests/testDecisionTreeFactor.cpp index ad8e9bd2a8..542d16b29c 100644 --- a/gtsam/discrete/tests/testDecisionTreeFactor.cpp +++ b/gtsam/discrete/tests/testDecisionTreeFactor.cpp @@ -34,7 +34,7 @@ TEST( DecisionTreeFactor, constructors) DiscreteKey X(0,2), Y(1,3), Z(2,2); // Create factors - DecisionTreeFactor f1(X, "2 8"); + DecisionTreeFactor f1(X, {2, 8}); DecisionTreeFactor f2(X & Y, "2 5 3 6 4 7"); DecisionTreeFactor f3(X & Y & Z, "2 5 3 6 4 7 25 55 35 65 45 75"); EXPECT_LONGS_EQUAL(1,f1.size()); diff --git a/python/gtsam/tests/test_DiscreteFactorGraph.py b/python/gtsam/tests/test_DiscreteFactorGraph.py index 9dafff33f0..dc2c7a4f5c 100644 --- a/python/gtsam/tests/test_DiscreteFactorGraph.py +++ b/python/gtsam/tests/test_DiscreteFactorGraph.py @@ -32,7 +32,7 @@ def test_evaluation(self): graph = DiscreteFactorGraph() # Add two unary factors (priors) - graph.add(P1, "0.9 0.3") + graph.add(P1, [0.9, 0.3]) graph.add(P2, "0.9 0.6") # Add a binary factor From 457d0748586b8e076d2baebb6928c7117a1ecd91 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 27 Dec 2021 13:01:29 -0500 Subject: [PATCH 13/19] likelihood --- gtsam/discrete/DiscreteConditional.cpp | 81 ++++++++++++++----- gtsam/discrete/DiscreteConditional.h | 12 ++- gtsam/discrete/discrete.i | 5 +- .../tests/testDiscreteConditional.cpp | 41 ++++++---- .../gtsam/tests/test_DiscreteConditional.py | 18 ++++- 5 files changed, 117 insertions(+), 40 deletions(-) diff --git a/gtsam/discrete/DiscreteConditional.cpp b/gtsam/discrete/DiscreteConditional.cpp index 2d3a5ddf8b..328af1ca35 100644 --- a/gtsam/discrete/DiscreteConditional.cpp +++ b/gtsam/discrete/DiscreteConditional.cpp @@ -97,45 +97,90 @@ bool DiscreteConditional::equals(const DiscreteFactor& other, } /* ******************************************************************************** */ -Potentials::ADT DiscreteConditional::choose( +static DiscreteConditional::ADT Choose(const DiscreteConditional& conditional, + const DiscreteValues& parentsValues) { + // Get the big decision tree with all the levels, and then go down the + // branches based on the value of the parent variables. + DiscreteConditional::ADT adt(conditional); + size_t value; + for (Key j : conditional.parents()) { + try { + value = parentsValues.at(j); + adt = adt.choose(j, value); // ADT keeps getting smaller. + } catch (exception&) { + parentsValues.print("parentsValues: "); + throw runtime_error("DiscreteConditional::choose: parent value missing"); + }; + } + return adt; +} + +/* ******************************************************************************** */ +DecisionTreeFactor::shared_ptr DiscreteConditional::choose( const DiscreteValues& parentsValues) const { // Get the big decision tree with all the levels, and then go down the // branches based on the value of the parent variables. - ADT pFS(*this); + ADT adt(*this); size_t value; for (Key j : parents()) { try { value = parentsValues.at(j); - pFS = pFS.choose(j, value); // ADT keeps getting smaller. + adt = adt.choose(j, value); // ADT keeps getting smaller. } catch (exception&) { - cout << "Key: " << j << " Value: " << value << endl; parentsValues.print("parentsValues: "); throw runtime_error("DiscreteConditional::choose: parent value missing"); }; } - return pFS; + + // Convert ADT to factor. + DiscreteKeys discreteKeys; + for (Key j : frontals()) { + discreteKeys.emplace_back(j, this->cardinality(j)); + } + return boost::make_shared(discreteKeys, adt); } /* ******************************************************************************** */ -DecisionTreeFactor::shared_ptr DiscreteConditional::chooseAsFactor( - const DiscreteValues& parentsValues) const { - ADT pFS = choose(parentsValues); +DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood( + const DiscreteValues& frontalValues) const { + // Get the big decision tree with all the levels, and then go down the + // branches based on the value of the frontal variables. + ADT adt(*this); + size_t value; + for (Key j : frontals()) { + try { + value = frontalValues.at(j); + adt = adt.choose(j, value); // ADT keeps getting smaller. + } catch (exception&) { + frontalValues.print("frontalValues: "); + throw runtime_error("DiscreteConditional::choose: frontal value missing"); + }; + } // Convert ADT to factor. - if (nrFrontals() != 1) { - throw std::runtime_error("Expected only one frontal variable in choose."); + DiscreteKeys discreteKeys; + for (Key j : parents()) { + discreteKeys.emplace_back(j, this->cardinality(j)); } - DiscreteKeys keys; - const Key frontalKey = keys_[0]; - size_t frontalCardinality = this->cardinality(frontalKey); - keys.push_back(DiscreteKey(frontalKey, frontalCardinality)); - return boost::make_shared(keys, pFS); + return boost::make_shared(discreteKeys, adt); +} + +/* ******************************************************************************** */ +DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood( + size_t value) const { + if (nrFrontals() != 1) + throw std::invalid_argument( + "Single value likelihood can only be invoked on single-variable " + "conditional"); + DiscreteValues values; + values.emplace(keys_[0], value); + return likelihood(values); } /* ******************************************************************************** */ void DiscreteConditional::solveInPlace(DiscreteValues* values) const { // TODO: Abhijit asks: is this really the fastest way? He thinks it is. - ADT pFS = choose(*values); // P(F|S=parentsValues) + ADT pFS = Choose(*this, *values); // P(F|S=parentsValues) // Initialize DiscreteValues mpe; @@ -177,7 +222,7 @@ void DiscreteConditional::sampleInPlace(DiscreteValues* values) const { size_t DiscreteConditional::solve(const DiscreteValues& parentsValues) const { // TODO: is this really the fastest way? I think it is. - ADT pFS = choose(parentsValues); // P(F|S=parentsValues) + ADT pFS = Choose(*this, parentsValues); // P(F|S=parentsValues) // Then, find the max over all remaining // TODO, only works for one key now, seems horribly slow this way @@ -203,7 +248,7 @@ size_t DiscreteConditional::sample(const DiscreteValues& parentsValues) const { static mt19937 rng(2); // random number generator // Get the correct conditional density - ADT pFS = choose(parentsValues); // P(F|S=parentsValues) + ADT pFS = Choose(*this, parentsValues); // P(F|S=parentsValues) // TODO(Duy): only works for one key now, seems horribly slow this way assert(nrFrontals() == 1); diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index e95dfb5157..c72f076d82 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -146,13 +146,17 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, return DecisionTreeFactor::shared_ptr(new DecisionTreeFactor(*this)); } - /** Restrict to given parent values, returns AlgebraicDecisionDiagram */ - ADT choose(const DiscreteValues& parentsValues) const; - /** Restrict to given parent values, returns DecisionTreeFactor */ - DecisionTreeFactor::shared_ptr chooseAsFactor( + DecisionTreeFactor::shared_ptr choose( const DiscreteValues& parentsValues) const; + /** Convert to a likelihood factor by providing value before bar. */ + DecisionTreeFactor::shared_ptr likelihood( + const DiscreteValues& frontalValues) const; + + /** Single variable version of likelihood. */ + DecisionTreeFactor::shared_ptr likelihood(size_t value) const; + /** * solve a conditional * @param parentsValues Known values of the parents diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 971250ba13..daacee3714 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -76,8 +76,11 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { string s = "Discrete Conditional: ", const gtsam::KeyFormatter& formatter = gtsam::DefaultKeyFormatter) const; gtsam::DecisionTreeFactor* toFactor() const; - gtsam::DecisionTreeFactor* chooseAsFactor( + gtsam::DecisionTreeFactor* choose( const gtsam::DiscreteValues& parentsValues) const; + gtsam::DecisionTreeFactor* likelihood( + const gtsam::DiscreteValues& frontalValues) const; + gtsam::DecisionTreeFactor* likelihood(size_t value) const; size_t solve(const gtsam::DiscreteValues& parentsValues) const; size_t sample(const gtsam::DiscreteValues& parentsValues) const; void solveInPlace(gtsam::DiscreteValues @parentsValues) const; diff --git a/gtsam/discrete/tests/testDiscreteConditional.cpp b/gtsam/discrete/tests/testDiscreteConditional.cpp index 604b0ce718..00ae1acd01 100644 --- a/gtsam/discrete/tests/testDiscreteConditional.cpp +++ b/gtsam/discrete/tests/testDiscreteConditional.cpp @@ -31,24 +31,21 @@ using namespace std; using namespace gtsam; /* ************************************************************************* */ -TEST( DiscreteConditional, constructors) -{ - DiscreteKey X(0, 2), Y(2, 3), Z(1, 2); // watch ordering ! - - DiscreteConditional::shared_ptr expected1 = // - boost::make_shared(X | Y = "1/1 2/3 1/4"); - EXPECT(expected1); - EXPECT_LONGS_EQUAL(0, *(expected1->beginFrontals())); - EXPECT_LONGS_EQUAL(2, *(expected1->beginParents())); - EXPECT(expected1->endParents() == expected1->end()); - EXPECT(expected1->endFrontals() == expected1->beginParents()); - +TEST(DiscreteConditional, constructors) { + DiscreteKey X(0, 2), Y(2, 3), Z(1, 2); // watch ordering ! + + DiscreteConditional expected(X | Y = "1/1 2/3 1/4"); + EXPECT_LONGS_EQUAL(0, *(expected.beginFrontals())); + EXPECT_LONGS_EQUAL(2, *(expected.beginParents())); + EXPECT(expected.endParents() == expected.end()); + EXPECT(expected.endFrontals() == expected.beginParents()); + DecisionTreeFactor f1(X & Y, "0.5 0.4 0.2 0.5 0.6 0.8"); DiscreteConditional actual1(1, f1); - EXPECT(assert_equal(*expected1, actual1, 1e-9)); + EXPECT(assert_equal(expected, actual1, 1e-9)); - DecisionTreeFactor f2(X & Y & Z, - "0.2 0.5 0.3 0.6 0.4 0.7 0.25 0.55 0.35 0.65 0.45 0.75"); + DecisionTreeFactor f2( + X & Y & Z, "0.2 0.5 0.3 0.6 0.4 0.7 0.25 0.55 0.35 0.65 0.45 0.75"); DiscreteConditional actual2(1, f2); EXPECT(assert_equal(f2 / *f2.sum(1), *actual2.toFactor(), 1e-9)); } @@ -108,6 +105,20 @@ TEST(DiscreteConditional, Combine) { EXPECT(assert_equal(expected, *actual, 1e-5)); } +/* ************************************************************************* */ +TEST(DiscreteConditional, likelihood) { + DiscreteKey X(0, 2), Y(1, 3); + DiscreteConditional conditional(X | Y = "2/8 4/6 5/5"); + + auto actual0 = conditional.likelihood(0); + DecisionTreeFactor expected0(Y, "0.2 0.4 0.5"); + EXPECT(assert_equal(expected0, *actual0, 1e-9)); + + auto actual1 = conditional.likelihood(1); + DecisionTreeFactor expected1(Y, "0.8 0.6 0.5"); + EXPECT(assert_equal(expected1, *actual1, 1e-9)); +} + /* ************************************************************************* */ // Check markdown representation looks as expected, no parents. TEST(DiscreteConditional, markdown_prior) { diff --git a/python/gtsam/tests/test_DiscreteConditional.py b/python/gtsam/tests/test_DiscreteConditional.py index 5e24dc40b9..0cd02ce6ad 100644 --- a/python/gtsam/tests/test_DiscreteConditional.py +++ b/python/gtsam/tests/test_DiscreteConditional.py @@ -13,12 +13,26 @@ import unittest -from gtsam import DiscreteConditional, DiscreteKeys +from gtsam import DecisionTreeFactor, DiscreteConditional, DiscreteKeys from gtsam.utils.test_case import GtsamTestCase class TestDiscreteConditional(GtsamTestCase): """Tests for Discrete Conditionals.""" + + def test_likelihood(self): + X = (0, 2) + Y = (1, 3) + conditional = DiscreteConditional(X, "2/8 4/6 5/5", Y) + + actual0 = conditional.likelihood(0) + expected0 = DecisionTreeFactor(Y, "0.2 0.4 0.5") + self.gtsamAssertEquals(actual0, expected0, 1e-9) + + actual1 = conditional.likelihood(1) + expected1 = DecisionTreeFactor(Y, "0.8 0.6 0.5") + self.gtsamAssertEquals(actual1, expected1, 1e-9) + def test_markdown(self): """Test whether the _repr_markdown_ method.""" @@ -32,7 +46,7 @@ def test_markdown(self): conditional = DiscreteConditional(A, parents, "0/1 1/3 1/1 3/1 0/1 1/0") expected = \ - " $P(A|B,C)$:\n" \ + " *P(A|B,C)*:\n\n" \ "|B|C|0|1|\n" \ "|:-:|:-:|:-:|:-:|\n" \ "|0|0|0|1|\n" \ From c622dde7a7f01f787d8fd63ec80b06fc8b06a529 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 27 Dec 2021 13:55:05 -0500 Subject: [PATCH 14/19] Fix typo in test --- python/gtsam/tests/test_DiscretePrior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/gtsam/tests/test_DiscretePrior.py b/python/gtsam/tests/test_DiscretePrior.py index 2b277ae918..4f017d66a4 100644 --- a/python/gtsam/tests/test_DiscretePrior.py +++ b/python/gtsam/tests/test_DiscretePrior.py @@ -46,7 +46,7 @@ def test_markdown(self): """Test the _repr_markdown_ method.""" prior = DiscretePrior(X, "2/3") - expected = " $P(0)$:\n" \ + expected = " *P(0)*:\n\n" \ "|0|value|\n" \ "|:-:|:-:|\n" \ "|0|0.4|\n" \ From 911819c7f2d4acd8c6076e63551b094ed82380f5 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 27 Dec 2021 13:55:11 -0500 Subject: [PATCH 15/19] enumerate --- gtsam/discrete/DecisionTreeFactor.cpp | 28 +++++++--- gtsam/discrete/DecisionTreeFactor.h | 3 ++ gtsam/discrete/discrete.i | 1 + .../discrete/tests/testDecisionTreeFactor.cpp | 22 +++++++- python/gtsam/tests/test_DecisionTreeFactor.py | 54 +++++++++++++++++++ 5 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 python/gtsam/tests/test_DecisionTreeFactor.py diff --git a/gtsam/discrete/DecisionTreeFactor.cpp b/gtsam/discrete/DecisionTreeFactor.cpp index 50b21fc768..768a37ab49 100644 --- a/gtsam/discrete/DecisionTreeFactor.cpp +++ b/gtsam/discrete/DecisionTreeFactor.cpp @@ -134,17 +134,33 @@ namespace gtsam { return boost::make_shared(dkeys, result); } + /* ************************************************************************* */ + std::vector> DecisionTreeFactor::enumerate() const { + // Get all possible assignments + std::vector> pairs; + for (auto& key : keys()) { + pairs.emplace_back(key, cardinalities_.at(key)); + } + std::vector> rpairs(pairs.rbegin(), pairs.rend()); + const auto assignments = cartesianProduct(rpairs); + + // Construct unordered_map with values + std::vector> result; + for (const auto& assignment : assignments) { + result.emplace_back(assignment, operator()(assignment)); + } + return result; + } + /* ************************************************************************* */ std::string DecisionTreeFactor::markdown( const KeyFormatter& keyFormatter) const { std::stringstream ss; // Print out header and construct argument for `cartesianProduct`. - std::vector> pairs; ss << "|"; for (auto& key : keys()) { ss << keyFormatter(key) << "|"; - pairs.emplace_back(key, cardinalities_.at(key)); } ss << "value|\n"; @@ -154,12 +170,12 @@ namespace gtsam { ss << ":-:|\n"; // Print out all rows. - std::vector> rpairs(pairs.rbegin(), pairs.rend()); - const auto assignments = cartesianProduct(rpairs); - for (const auto& assignment : assignments) { + auto rows = enumerate(); + for (const auto& kv : rows) { ss << "|"; + auto assignment = kv.first; for (auto& key : keys()) ss << assignment.at(key) << "|"; - ss << operator()(assignment) << "|\n"; + ss << kv.second << "|\n"; } return ss.str(); } diff --git a/gtsam/discrete/DecisionTreeFactor.h b/gtsam/discrete/DecisionTreeFactor.h index b5a0371197..ad81d9eb19 100644 --- a/gtsam/discrete/DecisionTreeFactor.h +++ b/gtsam/discrete/DecisionTreeFactor.h @@ -183,6 +183,9 @@ namespace gtsam { // Potentials::reduceWithInverse(inverseReduction); // } + /// Enumerate all values into a map from values to double. + std::vector> enumerate() const; + /// @} /// @name Wrapper support /// @{ diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index daacee3714..3b39374cb7 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -47,6 +47,7 @@ virtual class DecisionTreeFactor : gtsam::DiscreteFactor { gtsam::DefaultKeyFormatter) const; bool equals(const gtsam::DecisionTreeFactor& other, double tol = 1e-9) const; string dot(bool showZero = false) const; + std::vector> enumerate() const; string markdown(const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; }; diff --git a/gtsam/discrete/tests/testDecisionTreeFactor.cpp b/gtsam/discrete/tests/testDecisionTreeFactor.cpp index 542d16b29c..6af7ca7313 100644 --- a/gtsam/discrete/tests/testDecisionTreeFactor.cpp +++ b/gtsam/discrete/tests/testDecisionTreeFactor.cpp @@ -82,11 +82,29 @@ TEST( DecisionTreeFactor, sum_max) DecisionTreeFactor::shared_ptr actual22 = f2.sum(1); } +/* ************************************************************************* */ +// Check enumerate yields the correct list of assignment/value pairs. +TEST(DecisionTreeFactor, enumerate) { + DiscreteKey A(12, 3), B(5, 2); + DecisionTreeFactor f(A & B, "1 2 3 4 5 6"); + auto actual = f.enumerate(); + std::vector> expected; + DiscreteValues values; + for (size_t a : {0, 1, 2}) { + for (size_t b : {0, 1}) { + values[12] = a; + values[5] = b; + expected.emplace_back(values, f(values)); + } + } + EXPECT(actual == expected); +} + /* ************************************************************************* */ // Check markdown representation looks as expected. TEST(DecisionTreeFactor, markdown) { DiscreteKey A(12, 3), B(5, 2); - DecisionTreeFactor f1(A & B, "1 2 3 4 5 6"); + DecisionTreeFactor f(A & B, "1 2 3 4 5 6"); string expected = "|A|B|value|\n" "|:-:|:-:|:-:|\n" @@ -97,7 +115,7 @@ TEST(DecisionTreeFactor, markdown) { "|2|0|5|\n" "|2|1|6|\n"; auto formatter = [](Key key) { return key == 12 ? "A" : "B"; }; - string actual = f1.markdown(formatter); + string actual = f.markdown(formatter); EXPECT(actual == expected); } diff --git a/python/gtsam/tests/test_DecisionTreeFactor.py b/python/gtsam/tests/test_DecisionTreeFactor.py new file mode 100644 index 0000000000..586d1d142f --- /dev/null +++ b/python/gtsam/tests/test_DecisionTreeFactor.py @@ -0,0 +1,54 @@ +""" +GTSAM Copyright 2010-2021, Georgia Tech Research Corporation, +Atlanta, Georgia 30332-0415 +All Rights Reserved + +See LICENSE for the license information + +Unit tests for DecisionTreeFactors. +Author: Frank Dellaert +""" + +# pylint: disable=no-name-in-module, invalid-name + +import unittest + +from gtsam import DecisionTreeFactor, DecisionTreeFactor, DiscreteKeys +from gtsam.utils.test_case import GtsamTestCase + + +class TestDecisionTreeFactor(GtsamTestCase): + """Tests for DecisionTreeFactors.""" + + def setUp(self): + A = (12, 3) + B = (5, 2) + self.factor = DecisionTreeFactor(A, B, "1 2 3 4 5 6") + + def test_enumerate(self): + actual = self.factor.enumerate() + _, values = zip(*actual) + self.assertEqual(list(values), [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + + def test_markdown(self): + """Test whether the _repr_markdown_ method.""" + + expected = \ + "|A|B|value|\n" \ + "|:-:|:-:|:-:|\n" \ + "|0|0|1|\n" \ + "|0|1|2|\n" \ + "|1|0|3|\n" \ + "|1|1|4|\n" \ + "|2|0|5|\n" \ + "|2|1|6|\n" + + def formatter(x: int): + return "A" if x == 12 else "B" + + actual = self.factor._repr_markdown_(formatter) + self.assertEqual(actual, expected) + + +if __name__ == "__main__": + unittest.main() From 93e9756ef0959636e4c6901c2d874fea727b855a Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 28 Dec 2021 09:47:18 -0500 Subject: [PATCH 16/19] Removed all specialized constructors, because wrapper is awesome! --- gtsam/discrete/DecisionTreeFactor.cpp | 1 + gtsam/discrete/DiscreteConditional.cpp | 8 +++- gtsam/discrete/DiscreteConditional.h | 10 ----- gtsam/discrete/discrete.i | 14 ++----- python/gtsam/tests/test_DiscreteBayesNet.py | 12 +++--- python/gtsam/tests/test_DiscreteBayesTree.py | 42 +++++++------------ .../gtsam/tests/test_DiscreteConditional.py | 2 +- 7 files changed, 34 insertions(+), 55 deletions(-) diff --git a/gtsam/discrete/DecisionTreeFactor.cpp b/gtsam/discrete/DecisionTreeFactor.cpp index 768a37ab49..7aed00c57d 100644 --- a/gtsam/discrete/DecisionTreeFactor.cpp +++ b/gtsam/discrete/DecisionTreeFactor.cpp @@ -141,6 +141,7 @@ namespace gtsam { for (auto& key : keys()) { pairs.emplace_back(key, cardinalities_.at(key)); } + // Reverse to make cartesianProduct output a more natural ordering. std::vector> rpairs(pairs.rbegin(), pairs.rend()); const auto assignments = cartesianProduct(rpairs); diff --git a/gtsam/discrete/DiscreteConditional.cpp b/gtsam/discrete/DiscreteConditional.cpp index 328af1ca35..5279b2b8cb 100644 --- a/gtsam/discrete/DiscreteConditional.cpp +++ b/gtsam/discrete/DiscreteConditional.cpp @@ -107,7 +107,7 @@ static DiscreteConditional::ADT Choose(const DiscreteConditional& conditional, try { value = parentsValues.at(j); adt = adt.choose(j, value); // ADT keeps getting smaller. - } catch (exception&) { + } catch (std::out_of_range&) { parentsValues.print("parentsValues: "); throw runtime_error("DiscreteConditional::choose: parent value missing"); }; @@ -251,7 +251,11 @@ size_t DiscreteConditional::sample(const DiscreteValues& parentsValues) const { ADT pFS = Choose(*this, parentsValues); // P(F|S=parentsValues) // TODO(Duy): only works for one key now, seems horribly slow this way - assert(nrFrontals() == 1); + if (nrFrontals() != 1) { + throw std::invalid_argument( + "DiscreteConditional::sample can only be called on single variable " + "conditionals"); + } Key key = firstFrontalKey(); size_t nj = cardinality(key); vector p(nj); diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index c72f076d82..ea7f3de32f 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -85,16 +85,6 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, DiscreteConditional(const DiscreteKey& key, const std::string& spec) : DiscreteConditional(Signature(key, {}, spec)) {} - /// Single-parent specialization - DiscreteConditional(const DiscreteKey& key, const std::string& spec, - const DiscreteKey& parent1) - : DiscreteConditional(Signature(key, {parent1}, spec)) {} - - /// Two-parent specialization - DiscreteConditional(const DiscreteKey& key, const std::string& spec, - const DiscreteKey& parent1, const DiscreteKey& parent2) - : DiscreteConditional(Signature(key, {parent1, parent2}, spec)) {} - /** construct P(X|Y)=P(X,Y)/P(Y) from P(X,Y) and P(Y) */ DiscreteConditional(const DecisionTreeFactor& joint, const DecisionTreeFactor& marginal); diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 3b39374cb7..3437a80a02 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -57,13 +57,10 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { DiscreteConditional(); DiscreteConditional(size_t nFrontals, const gtsam::DecisionTreeFactor& f); DiscreteConditional(const gtsam::DiscreteKey& key, string spec); - DiscreteConditional(const gtsam::DiscreteKey& key, string spec, - const gtsam::DiscreteKey& parent1); - DiscreteConditional(const gtsam::DiscreteKey& key, string spec, - const gtsam::DiscreteKey& parent1, - const gtsam::DiscreteKey& parent2); DiscreteConditional(const gtsam::DiscreteKey& key, const gtsam::DiscreteKeys& parents, string spec); + DiscreteConditional(const gtsam::DiscreteKey& key, + const std::vector& parents, string spec); DiscreteConditional(const gtsam::DecisionTreeFactor& joint, const gtsam::DecisionTreeFactor& marginal); DiscreteConditional(const gtsam::DecisionTreeFactor& joint, @@ -109,13 +106,10 @@ class DiscreteBayesNet { DiscreteBayesNet(); void add(const gtsam::DiscreteConditional& s); void add(const gtsam::DiscreteKey& key, string spec); - void add(const gtsam::DiscreteKey& key, string spec, - const gtsam::DiscreteKey& parent1); - void add(const gtsam::DiscreteKey& key, string spec, - const gtsam::DiscreteKey& parent1, - const gtsam::DiscreteKey& parent2); void add(const gtsam::DiscreteKey& key, const gtsam::DiscreteKeys& parents, string spec); + void add(const gtsam::DiscreteKey& key, + const std::vector& parents, string spec); bool empty() const; size_t size() const; gtsam::KeySet keys() const; diff --git a/python/gtsam/tests/test_DiscreteBayesNet.py b/python/gtsam/tests/test_DiscreteBayesNet.py index 706cdf93d0..bdd5a05464 100644 --- a/python/gtsam/tests/test_DiscreteBayesNet.py +++ b/python/gtsam/tests/test_DiscreteBayesNet.py @@ -57,14 +57,14 @@ def test_Asia(self): asia.add(Asia, "99/1") asia.add(Smoking, "50/50") - asia.add(Tuberculosis, "99/1 95/5", Asia) - asia.add(LungCancer, "99/1 90/10", Smoking) - asia.add(Bronchitis, "70/30 40/60", Smoking) + asia.add(Tuberculosis, [Asia], "99/1 95/5") + asia.add(LungCancer, [Smoking], "99/1 90/10") + asia.add(Bronchitis, [Smoking], "70/30 40/60") - asia.add(Either, "F T T T", Tuberculosis, LungCancer) + asia.add(Either, [Tuberculosis, LungCancer], "F T T T") - asia.add(XRay, "95/5 2/98", Either) - asia.add(Dyspnea, "9/1 2/8 3/7 1/9", Either, Bronchitis) + asia.add(XRay, [Either], "95/5 2/98") + asia.add(Dyspnea, [Either, Bronchitis], "9/1 2/8 3/7 1/9") # Convert to factor graph fg = DiscreteFactorGraph(asia) diff --git a/python/gtsam/tests/test_DiscreteBayesTree.py b/python/gtsam/tests/test_DiscreteBayesTree.py index d87734de99..b1ed4fe696 100644 --- a/python/gtsam/tests/test_DiscreteBayesTree.py +++ b/python/gtsam/tests/test_DiscreteBayesTree.py @@ -14,20 +14,10 @@ import unittest from gtsam import (DiscreteBayesNet, DiscreteBayesTreeClique, - DiscreteConditional, DiscreteFactorGraph, DiscreteKeys, - Ordering) + DiscreteConditional, DiscreteFactorGraph, Ordering) from gtsam.utils.test_case import GtsamTestCase -def P(*args): - """ Create a DiscreteKeys instances from a variable number of DiscreteKey pairs.""" - # TODO: We can make life easier by providing variable argument functions in C++ itself. - dks = DiscreteKeys() - for key in args: - dks.push_back(key) - return dks - - class TestDiscreteBayesNet(GtsamTestCase): """Tests for Discrete Bayes Nets.""" @@ -40,25 +30,25 @@ def test_elimination(self): # Create thin-tree Bayesnet. bayesNet = DiscreteBayesNet() - bayesNet.add(keys[0], P(keys[8], keys[12]), "2/3 1/4 3/2 4/1") - bayesNet.add(keys[1], P(keys[8], keys[12]), "4/1 2/3 3/2 1/4") - bayesNet.add(keys[2], P(keys[9], keys[12]), "1/4 8/2 2/3 4/1") - bayesNet.add(keys[3], P(keys[9], keys[12]), "1/4 2/3 3/2 4/1") + bayesNet.add(keys[0], [keys[8], keys[12]], "2/3 1/4 3/2 4/1") + bayesNet.add(keys[1], [keys[8], keys[12]], "4/1 2/3 3/2 1/4") + bayesNet.add(keys[2], [keys[9], keys[12]], "1/4 8/2 2/3 4/1") + bayesNet.add(keys[3], [keys[9], keys[12]], "1/4 2/3 3/2 4/1") - bayesNet.add(keys[4], P(keys[10], keys[13]), "2/3 1/4 3/2 4/1") - bayesNet.add(keys[5], P(keys[10], keys[13]), "4/1 2/3 3/2 1/4") - bayesNet.add(keys[6], P(keys[11], keys[13]), "1/4 3/2 2/3 4/1") - bayesNet.add(keys[7], P(keys[11], keys[13]), "1/4 2/3 3/2 4/1") + bayesNet.add(keys[4], [keys[10], keys[13]], "2/3 1/4 3/2 4/1") + bayesNet.add(keys[5], [keys[10], keys[13]], "4/1 2/3 3/2 1/4") + bayesNet.add(keys[6], [keys[11], keys[13]], "1/4 3/2 2/3 4/1") + bayesNet.add(keys[7], [keys[11], keys[13]], "1/4 2/3 3/2 4/1") - bayesNet.add(keys[8], P(keys[12], keys[14]), "T 1/4 3/2 4/1") - bayesNet.add(keys[9], P(keys[12], keys[14]), "4/1 2/3 F 1/4") - bayesNet.add(keys[10], P(keys[13], keys[14]), "1/4 3/2 2/3 4/1") - bayesNet.add(keys[11], P(keys[13], keys[14]), "1/4 2/3 3/2 4/1") + bayesNet.add(keys[8], [keys[12], keys[14]], "T 1/4 3/2 4/1") + bayesNet.add(keys[9], [keys[12], keys[14]], "4/1 2/3 F 1/4") + bayesNet.add(keys[10], [keys[13], keys[14]], "1/4 3/2 2/3 4/1") + bayesNet.add(keys[11], [keys[13], keys[14]], "1/4 2/3 3/2 4/1") - bayesNet.add(keys[12], P(keys[14]), "3/1 3/1") - bayesNet.add(keys[13], P(keys[14]), "1/3 3/1") + bayesNet.add(keys[12], [keys[14]], "3/1 3/1") + bayesNet.add(keys[13], [keys[14]], "1/3 3/1") - bayesNet.add(keys[14], P(), "1/3") + bayesNet.add(keys[14], "1/3") # Create a factor graph out of the Bayes net. factorGraph = DiscreteFactorGraph(bayesNet) diff --git a/python/gtsam/tests/test_DiscreteConditional.py b/python/gtsam/tests/test_DiscreteConditional.py index 0cd02ce6ad..44d25461fd 100644 --- a/python/gtsam/tests/test_DiscreteConditional.py +++ b/python/gtsam/tests/test_DiscreteConditional.py @@ -23,7 +23,7 @@ class TestDiscreteConditional(GtsamTestCase): def test_likelihood(self): X = (0, 2) Y = (1, 3) - conditional = DiscreteConditional(X, "2/8 4/6 5/5", Y) + conditional = DiscreteConditional(X, [Y], "2/8 4/6 5/5") actual0 = conditional.likelihood(0) expected0 = DecisionTreeFactor(Y, "0.2 0.4 0.5") From 340ac7569ddadd40f817121a0a478cf9b188d8fc Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 28 Dec 2021 13:00:14 -0500 Subject: [PATCH 17/19] Removed 2 and 3 key constructors for DecisionTreeFactor because wrapper is awesome! --- gtsam/discrete/DecisionTreeFactor.h | 12 ---------- gtsam/discrete/discrete.i | 23 ++++++++++--------- gtsam_unstable/discrete/Scheduler.cpp | 6 ++--- python/gtsam/tests/test_DecisionTreeFactor.py | 2 +- .../gtsam/tests/test_DiscreteFactorGraph.py | 10 ++++---- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/gtsam/discrete/DecisionTreeFactor.h b/gtsam/discrete/DecisionTreeFactor.h index ad81d9eb19..f90af56dd0 100644 --- a/gtsam/discrete/DecisionTreeFactor.h +++ b/gtsam/discrete/DecisionTreeFactor.h @@ -70,18 +70,6 @@ namespace gtsam { DecisionTreeFactor(const DiscreteKey& key, const std::vector& row) : DecisionTreeFactor(DiscreteKeys{key}, row) {} - /// Two-key specialization - template - DecisionTreeFactor(const DiscreteKey& key1, const DiscreteKey& key2, - SOURCE table) - : DecisionTreeFactor({key1, key2}, table) {} - - /// Three-key specialization - template - DecisionTreeFactor(const DiscreteKey& key1, const DiscreteKey& key2, - const DiscreteKey& key3, SOURCE table) - : DecisionTreeFactor({key1, key2, key3}, table) {} - /** Construct from a DiscreteConditional type */ DecisionTreeFactor(const DiscreteConditional& c); diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index 3437a80a02..da3179a257 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -32,16 +32,16 @@ class DiscreteFactor { #include virtual class DecisionTreeFactor : gtsam::DiscreteFactor { DecisionTreeFactor(); - DecisionTreeFactor(const gtsam::DiscreteKeys& keys, string table); + DecisionTreeFactor(const gtsam::DiscreteKey& key, const std::vector& spec); - DecisionTreeFactor(const gtsam::DiscreteKey& key, const std::string& spec); - DecisionTreeFactor(const gtsam::DiscreteKey& key1, - const gtsam::DiscreteKey& key2, const std::string& spec); - DecisionTreeFactor(const gtsam::DiscreteKey& key1, - const gtsam::DiscreteKey& key2, - const gtsam::DiscreteKey& key3, const std::string& spec); + DecisionTreeFactor(const gtsam::DiscreteKey& key, string table); + + DecisionTreeFactor(const gtsam::DiscreteKeys& keys, string table); + DecisionTreeFactor(const std::vector& keys, string table); + DecisionTreeFactor(const gtsam::DiscreteConditional& c); + void print(string s = "DecisionTreeFactor\n", const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; @@ -174,12 +174,13 @@ class DotWriter { class DiscreteFactorGraph { DiscreteFactorGraph(); DiscreteFactorGraph(const gtsam::DiscreteBayesNet& bayesNet); - - void add(const gtsam::DiscreteKey& j, const std::vector& spec); + void add(const gtsam::DiscreteKey& j, string table); - void add(const gtsam::DiscreteKey& j1, const gtsam::DiscreteKey& j2, string table); + void add(const gtsam::DiscreteKey& j, const std::vector& spec); + void add(const gtsam::DiscreteKeys& keys, string table); - + void add(const std::vector& keys, string table); + bool empty() const; size_t size() const; gtsam::KeySet keys() const; diff --git a/gtsam_unstable/discrete/Scheduler.cpp b/gtsam_unstable/discrete/Scheduler.cpp index 36c1ddda58..e34613c3b3 100644 --- a/gtsam_unstable/discrete/Scheduler.cpp +++ b/gtsam_unstable/discrete/Scheduler.cpp @@ -133,10 +133,10 @@ void Scheduler::addStudentSpecificConstraints(size_t i, Potentials::ADT p(dummy & areaKey, available_); // available_ is Doodle string Potentials::ADT q = p.choose(dummyIndex, *slot); - DiscreteFactor::shared_ptr f(new DecisionTreeFactor(areaKey, q)); - CSP::push_back(f); + CSP::add(areaKey, q); } else { - CSP::add(s.key_, areaKey, available_); // available_ is Doodle string + DiscreteKeys keys {s.key_, areaKey}; + CSP::add(keys, available_); // available_ is Doodle string } } diff --git a/python/gtsam/tests/test_DecisionTreeFactor.py b/python/gtsam/tests/test_DecisionTreeFactor.py index 586d1d142f..12a60d5cb1 100644 --- a/python/gtsam/tests/test_DecisionTreeFactor.py +++ b/python/gtsam/tests/test_DecisionTreeFactor.py @@ -23,7 +23,7 @@ class TestDecisionTreeFactor(GtsamTestCase): def setUp(self): A = (12, 3) B = (5, 2) - self.factor = DecisionTreeFactor(A, B, "1 2 3 4 5 6") + self.factor = DecisionTreeFactor([A, B], "1 2 3 4 5 6") def test_enumerate(self): actual = self.factor.enumerate() diff --git a/python/gtsam/tests/test_DiscreteFactorGraph.py b/python/gtsam/tests/test_DiscreteFactorGraph.py index dc2c7a4f5c..1ba145e096 100644 --- a/python/gtsam/tests/test_DiscreteFactorGraph.py +++ b/python/gtsam/tests/test_DiscreteFactorGraph.py @@ -36,7 +36,7 @@ def test_evaluation(self): graph.add(P2, "0.9 0.6") # Add a binary factor - graph.add(P1, P2, "4 1 10 4") + graph.add([P1, P2], "4 1 10 4") # Instantiate Values assignment = DiscreteValues() @@ -85,8 +85,8 @@ def test_optimize(self): # A simple factor graph (A)-fAC-(C)-fBC-(B) # with smoothness priors graph = DiscreteFactorGraph() - graph.add(A, C, "3 1 1 3") - graph.add(C, B, "3 1 1 3") + graph.add([A, C], "3 1 1 3") + graph.add([C, B], "3 1 1 3") # Test optimization expectedValues = DiscreteValues() @@ -105,8 +105,8 @@ def test_MPE(self): # Create Factor graph graph = DiscreteFactorGraph() - graph.add(C, A, "0.2 0.8 0.3 0.7") - graph.add(C, B, "0.1 0.9 0.4 0.6") + graph.add([C, A], "0.2 0.8 0.3 0.7") + graph.add([C, B], "0.1 0.9 0.4 0.6") actualMPE = graph.optimize() From a6ea6f9153dd5856afb3dd77dbc2d745b1a86544 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 28 Dec 2021 17:49:18 -0500 Subject: [PATCH 18/19] single-value sample() --- gtsam/discrete/DiscreteConditional.cpp | 15 +++++++++++++-- gtsam/discrete/DiscreteConditional.h | 6 +++++- gtsam/discrete/discrete.i | 1 + python/gtsam/tests/test_DiscreteConditional.py | 5 ++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/gtsam/discrete/DiscreteConditional.cpp b/gtsam/discrete/DiscreteConditional.cpp index 5279b2b8cb..46d5509e06 100644 --- a/gtsam/discrete/DiscreteConditional.cpp +++ b/gtsam/discrete/DiscreteConditional.cpp @@ -167,13 +167,13 @@ DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood( /* ******************************************************************************** */ DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood( - size_t value) const { + size_t parent_value) const { if (nrFrontals() != 1) throw std::invalid_argument( "Single value likelihood can only be invoked on single-variable " "conditional"); DiscreteValues values; - values.emplace(keys_[0], value); + values.emplace(keys_[0], parent_value); return likelihood(values); } @@ -271,6 +271,17 @@ size_t DiscreteConditional::sample(const DiscreteValues& parentsValues) const { return distribution(rng); } +/* ******************************************************************************** */ +size_t DiscreteConditional::sample(size_t parent_value) const { + if (nrParents() != 1) + throw std::invalid_argument( + "Single value sample() can only be invoked on single-parent " + "conditional"); + DiscreteValues values; + values.emplace(keys_.back(), parent_value); + return sample(values); +} + /* ************************************************************************* */ std::string DiscreteConditional::markdown( const KeyFormatter& keyFormatter) const { diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index ea7f3de32f..d21e3ae264 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -145,7 +145,7 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, const DiscreteValues& frontalValues) const; /** Single variable version of likelihood. */ - DecisionTreeFactor::shared_ptr likelihood(size_t value) const; + DecisionTreeFactor::shared_ptr likelihood(size_t parent_value) const; /** * solve a conditional @@ -161,6 +161,10 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, */ size_t sample(const DiscreteValues& parentsValues) const; + + /// Single value version. + size_t sample(size_t parent_value) const; + /// @} /// @name Advanced Interface /// @{ diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index da3179a257..36caccfc83 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -81,6 +81,7 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { gtsam::DecisionTreeFactor* likelihood(size_t value) const; size_t solve(const gtsam::DiscreteValues& parentsValues) const; size_t sample(const gtsam::DiscreteValues& parentsValues) const; + size_t sample(size_t value) const; void solveInPlace(gtsam::DiscreteValues @parentsValues) const; void sampleInPlace(gtsam::DiscreteValues @parentsValues) const; string markdown(const gtsam::KeyFormatter& keyFormatter = diff --git a/python/gtsam/tests/test_DiscreteConditional.py b/python/gtsam/tests/test_DiscreteConditional.py index 44d25461fd..1b2ce70cd7 100644 --- a/python/gtsam/tests/test_DiscreteConditional.py +++ b/python/gtsam/tests/test_DiscreteConditional.py @@ -20,7 +20,7 @@ class TestDiscreteConditional(GtsamTestCase): """Tests for Discrete Conditionals.""" - def test_likelihood(self): + def test_single_value_versions(self): X = (0, 2) Y = (1, 3) conditional = DiscreteConditional(X, [Y], "2/8 4/6 5/5") @@ -33,6 +33,9 @@ def test_likelihood(self): expected1 = DecisionTreeFactor(Y, "0.8 0.6 0.5") self.gtsamAssertEquals(actual1, expected1, 1e-9) + actual = conditional.sample(2) + self.assertIsInstance(actual, int) + def test_markdown(self): """Test whether the _repr_markdown_ method.""" From b604d1ca29f3048c48409ed2483452b0b64e874c Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 28 Dec 2021 17:55:01 -0500 Subject: [PATCH 19/19] Version logic + version bump to 4.2a0 --- CMakeLists.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fd5d521c2..74019da446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,12 +9,18 @@ endif() # Set the version number for the library set (GTSAM_VERSION_MAJOR 4) -set (GTSAM_VERSION_MINOR 1) +set (GTSAM_VERSION_MINOR 2) set (GTSAM_VERSION_PATCH 0) +set (GTSAM_PRERELEASE_VERSION "a0") math (EXPR GTSAM_VERSION_NUMERIC "10000 * ${GTSAM_VERSION_MAJOR} + 100 * ${GTSAM_VERSION_MINOR} + ${GTSAM_VERSION_PATCH}") -set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}") -set (CMAKE_PROJECT_VERSION ${GTSAM_VERSION_STRING}) +if (${GTSAM_VERSION_PATCH} EQUAL 0) + set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}${GTSAM_PRERELEASE_VERSION}") +else() + set (GTSAM_VERSION_STRING "${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}${GTSAM_PRERELEASE_VERSION}") +endif() +message(STATUS "GTSAM Version: ${GTSAM_VERSION_STRING}") + set (CMAKE_PROJECT_VERSION_MAJOR ${GTSAM_VERSION_MAJOR}) set (CMAKE_PROJECT_VERSION_MINOR ${GTSAM_VERSION_MINOR}) set (CMAKE_PROJECT_VERSION_PATCH ${GTSAM_VERSION_PATCH})