diff --git a/examples/UGM_small.cpp b/examples/UGM_small.cpp index 3829a5c917..24bd0c0ba7 100644 --- a/examples/UGM_small.cpp +++ b/examples/UGM_small.cpp @@ -50,7 +50,8 @@ int main(int argc, char** argv) { // Print the UGM distribution cout << "\nUGM distribution:" << endl; - auto allPosbValues = cartesianProduct(Cathy & Heather & Mark & Allison); + auto allPosbValues = + DiscreteValues::CartesianProduct(Cathy & Heather & Mark & Allison); for (size_t i = 0; i < allPosbValues.size(); ++i) { DiscreteFactor::Values values = allPosbValues[i]; double prodPot = graph(values); diff --git a/gtsam/discrete/Assignment.h b/gtsam/discrete/Assignment.h index 3665d6dfa9..cdbf0a2e96 100644 --- a/gtsam/discrete/Assignment.h +++ b/gtsam/discrete/Assignment.h @@ -19,32 +19,30 @@ #pragma once #include -#include #include - +#include +#include namespace gtsam { - /** - * An assignment from labels to value index (size_t). - * Assigns to each label a value. Implemented as a simple map. - * A discrete factor takes an Assignment and returns a value. - */ - template - class Assignment: public std::map { - public: - void print(const std::string& s = "Assignment: ") const { - std::cout << s << ": "; - for(const typename Assignment::value_type& keyValue: *this) - std::cout << "(" << keyValue.first << ", " << keyValue.second << ")"; - std::cout << std::endl; - } - - bool equals(const Assignment& other, double tol = 1e-9) const { - return (*this == other); - } - }; //Assignment +/** + * An assignment from labels to value index (size_t). + * Assigns to each label a value. Implemented as a simple map. + * A discrete factor takes an Assignment and returns a value. + */ +template +class Assignment : public std::map { + public: + void print(const std::string& s = "Assignment: ") const { + std::cout << s << ": "; + for (const typename Assignment::value_type& keyValue : *this) + std::cout << "(" << keyValue.first << ", " << keyValue.second << ")"; + std::cout << std::endl; + } + bool equals(const Assignment& other, double tol = 1e-9) const { + return (*this == other); + } /** * @brief Get Cartesian product consisting all possible configurations @@ -58,29 +56,28 @@ namespace gtsam { * variables with each having cardinalities 4, we get 4096 possible * configurations!! */ - template - std::vector > cartesianProduct( - const std::vector >& keys) { - std::vector > allPossValues; - Assignment values; + template > + static std::vector CartesianProduct( + const std::vector>& keys) { + std::vector allPossValues; + Derived values; typedef std::pair DiscreteKey; - for(const DiscreteKey& key: keys) - values[key.first] = 0; //Initialize from 0 + for (const DiscreteKey& key : keys) + values[key.first] = 0; // Initialize from 0 while (1) { allPossValues.push_back(values); size_t j = 0; for (j = 0; j < keys.size(); j++) { L idx = keys[j].first; values[idx]++; - if (values[idx] < keys[j].second) - break; - //Wrap condition + if (values[idx] < keys[j].second) break; + // Wrap condition values[idx] = 0; } - if (j == keys.size()) - break; + if (j == keys.size()) break; } return allPossValues; } +}; // Assignment -} // namespace gtsam +} // namespace gtsam diff --git a/gtsam/discrete/DecisionTreeFactor.cpp b/gtsam/discrete/DecisionTreeFactor.cpp index 2607a80ef5..ad4cbad434 100644 --- a/gtsam/discrete/DecisionTreeFactor.cpp +++ b/gtsam/discrete/DecisionTreeFactor.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace std; @@ -150,9 +151,9 @@ namespace gtsam { for (auto& key : keys()) { pairs.emplace_back(key, cardinalities_.at(key)); } - // Reverse to make cartesianProduct output a more natural ordering. + // Reverse to make cartesian product output a more natural ordering. std::vector> rpairs(pairs.rbegin(), pairs.rend()); - const auto assignments = cartesianProduct(rpairs); + const auto assignments = DiscreteValues::CartesianProduct(rpairs); // Construct unordered_map with values std::vector> result; @@ -187,12 +188,13 @@ namespace gtsam { return ADT::dot(keyFormatter, valueFormatter, showZero); } + // Print out header. /* ************************************************************************* */ string DecisionTreeFactor::markdown(const KeyFormatter& keyFormatter, const Names& names) const { stringstream ss; - // Print out header and construct argument for `cartesianProduct`. + // Print out header. ss << "|"; for (auto& key : keys()) { ss << keyFormatter(key) << "|"; @@ -211,18 +213,54 @@ namespace gtsam { auto assignment = kv.first; for (auto& key : keys()) { size_t index = assignment.at(key); - ss << Translate(names, key, index) << "|"; + ss << DiscreteValues::Translate(names, key, index) << "|"; } ss << kv.second << "|\n"; } return ss.str(); } + /* ************************************************************************ */ + string DecisionTreeFactor::html(const KeyFormatter& keyFormatter, + const Names& names) const { + stringstream ss; + + // Print out preamble. + ss << "
\n\n \n"; + + // Print out header row. + ss << " "; + for (auto& key : keys()) { + ss << ""; + } + ss << "\n"; + + // Finish header and start body. + ss << " \n \n"; + + // Print out all rows. + auto rows = enumerate(); + for (const auto& kv : rows) { + ss << " "; + auto assignment = kv.first; + for (auto& key : keys()) { + size_t index = assignment.at(key); + ss << ""; + } + ss << ""; // value + ss << "\n"; + } + ss << " \n
" << keyFormatter(key) << "value
" << DiscreteValues::Translate(names, key, index) << "" << kv.second << "
\n
"; + return ss.str(); + } + + /* ************************************************************************* */ DecisionTreeFactor::DecisionTreeFactor(const DiscreteKeys &keys, const vector &table) : DiscreteFactor(keys.indices()), AlgebraicDecisionTree(keys, table), cardinalities_(keys.cardinalities()) { } + /* ************************************************************************* */ DecisionTreeFactor::DecisionTreeFactor(const DiscreteKeys &keys, const string &table) : DiscreteFactor(keys.indices()), AlgebraicDecisionTree(keys, table), cardinalities_(keys.cardinalities()) { diff --git a/gtsam/discrete/DecisionTreeFactor.h b/gtsam/discrete/DecisionTreeFactor.h index f7c50d5b5f..b5f6c0c4af 100644 --- a/gtsam/discrete/DecisionTreeFactor.h +++ b/gtsam/discrete/DecisionTreeFactor.h @@ -211,6 +211,16 @@ namespace gtsam { std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, const Names& names = {}) const override; + /** + * @brief Render as html table + * + * @param keyFormatter GTSAM-style Key formatter. + * @param names optional, category names corresponding to choices. + * @return std::string a html string. + */ + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const override; + /// @} }; diff --git a/gtsam/discrete/DiscreteBayesNet.cpp b/gtsam/discrete/DiscreteBayesNet.cpp index 510fb56389..c0dfd747c3 100644 --- a/gtsam/discrete/DiscreteBayesNet.cpp +++ b/gtsam/discrete/DiscreteBayesNet.cpp @@ -61,16 +61,29 @@ namespace gtsam { return result; } - /* ************************************************************************* */ + /* *********************************************************************** */ std::string DiscreteBayesNet::markdown( const KeyFormatter& keyFormatter, const DiscreteFactor::Names& names) const { using std::endl; std::stringstream ss; ss << "`DiscreteBayesNet` of size " << size() << endl << endl; - for(const DiscreteConditional::shared_ptr& conditional: *this) + for (const DiscreteConditional::shared_ptr& conditional : *this) ss << conditional->markdown(keyFormatter, names) << endl; return ss.str(); } + + /* *********************************************************************** */ + std::string DiscreteBayesNet::html( + const KeyFormatter& keyFormatter, + const DiscreteFactor::Names& names) const { + using std::endl; + std::stringstream ss; + ss << "

DiscreteBayesNet of size " << size() << "

"; + for (const DiscreteConditional::shared_ptr& conditional : *this) + ss << conditional->html(keyFormatter, names) << endl; + return ss.str(); + } + /* ************************************************************************* */ } // namespace diff --git a/gtsam/discrete/DiscreteBayesNet.h b/gtsam/discrete/DiscreteBayesNet.h index 5332b51dd0..17dfe2c5ff 100644 --- a/gtsam/discrete/DiscreteBayesNet.h +++ b/gtsam/discrete/DiscreteBayesNet.h @@ -18,13 +18,16 @@ #pragma once -#include -#include -#include +#include +#include #include #include -#include -#include + +#include +#include +#include +#include +#include namespace gtsam { @@ -107,13 +110,17 @@ namespace gtsam { /// @name Wrapper support /// @{ - /// Render as markdown table. + /// Render as markdown tables. std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, const DiscreteFactor::Names& names = {}) const; + /// Render as html tables. + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const DiscreteFactor::Names& names = {}) const; + /// @} - private: + private: /** Serialization function */ friend class boost::serialization::access; template diff --git a/gtsam/discrete/DiscreteBayesTree.cpp b/gtsam/discrete/DiscreteBayesTree.cpp index 07d6e0f0ee..139292eeef 100644 --- a/gtsam/discrete/DiscreteBayesTree.cpp +++ b/gtsam/discrete/DiscreteBayesTree.cpp @@ -72,5 +72,23 @@ namespace gtsam { return ss.str(); } + /* **************************************************************************/ + std::string DiscreteBayesTree::html( + const KeyFormatter& keyFormatter, + const DiscreteFactor::Names& names) const { + using std::endl; + std::stringstream ss; + ss << "

DiscreteBayesTree of size " << nodes_.size() + << "

"; + auto visitor = [&](const DiscreteBayesTreeClique::shared_ptr& clique, + size_t& indent) { + ss << clique->conditional()->html(keyFormatter, names); + return indent + 1; + }; + size_t indent; + treeTraversal::DepthFirstForest(*this, indent, visitor); + return ss.str(); + } + /* **************************************************************************/ } // namespace gtsam diff --git a/gtsam/discrete/DiscreteBayesTree.h b/gtsam/discrete/DiscreteBayesTree.h index 6189f25d54..809ce9c835 100644 --- a/gtsam/discrete/DiscreteBayesTree.h +++ b/gtsam/discrete/DiscreteBayesTree.h @@ -92,10 +92,14 @@ class GTSAM_EXPORT DiscreteBayesTree /// @name Wrapper support /// @{ - /// Render as markdown table. + /// Render as markdown tables. std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, const DiscreteFactor::Names& names = {}) const; + /// Render as html tables. + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const DiscreteFactor::Names& names = {}) const; + /// @} }; diff --git a/gtsam/discrete/DiscreteConditional.cpp b/gtsam/discrete/DiscreteConditional.cpp index 951c0b6cab..0bdc7d7b5a 100644 --- a/gtsam/discrete/DiscreteConditional.cpp +++ b/gtsam/discrete/DiscreteConditional.cpp @@ -29,9 +29,12 @@ #include #include #include +#include using namespace std; - +using std::stringstream; +using std::vector; +using std::pair; namespace gtsam { // Instantiate base class @@ -177,26 +180,21 @@ DecisionTreeFactor::shared_ptr DiscreteConditional::likelihood( 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(*this, *values); // P(F|S=parentsValues) + // TODO(Abhijit): is this really the fastest way? He thinks it is. + ADT pFS = Choose(*this, *values); // P(F|S=parentsValues) // Initialize DiscreteValues mpe; double maxP = 0; - DiscreteKeys keys; - for(Key idx: frontals()) { - DiscreteKey dk(idx, cardinality(idx)); - keys & dk; - } // Get all Possible Configurations - const auto allPosbValues = cartesianProduct(keys); + const auto allPosbValues = frontalAssignments(); // Find the MPE - for(const auto& frontalVals: allPosbValues) { - double pValueS = pFS(frontalVals); // P(F=value|S=parentsValues) + for (const auto& frontalVals : allPosbValues) { + double pValueS = pFS(frontalVals); // P(F=value|S=parentsValues) // Update MPE solution if better if (pValueS > maxP) { maxP = pValueS; @@ -204,8 +202,8 @@ void DiscreteConditional::solveInPlace(DiscreteValues* values) const { } } - //set values (inPlace) to mpe - for(Key j: frontals()) { + // set values (inPlace) to mpe + for (Key j : frontals()) { (*values)[j] = mpe[j]; } } @@ -292,57 +290,70 @@ size_t DiscreteConditional::sample() const { } /* ************************************************************************* */ -std::string DiscreteConditional::markdown(const KeyFormatter& keyFormatter, - const Names& names) const { - std::stringstream ss; +vector DiscreteConditional::frontalAssignments() const { + vector> pairs; + for (Key key : frontals()) pairs.emplace_back(key, cardinalities_.at(key)); + vector> rpairs(pairs.rbegin(), pairs.rend()); + return DiscreteValues::CartesianProduct(rpairs); +} + +/* ************************************************************************* */ +vector DiscreteConditional::allAssignments() const { + vector> pairs; + for (Key key : parents()) pairs.emplace_back(key, cardinalities_.at(key)); + for (Key key : frontals()) pairs.emplace_back(key, cardinalities_.at(key)); + vector> rpairs(pairs.rbegin(), pairs.rend()); + return DiscreteValues::CartesianProduct(rpairs); +} - // Print out signature. - ss << " *P("; +/* ************************************************************************* */ +// Print out signature. +static void streamSignature(const DiscreteConditional& conditional, + const KeyFormatter& keyFormatter, + stringstream* ss) { + *ss << "P("; bool first = true; - for (Key key : frontals()) { - if (!first) ss << ","; - ss << keyFormatter(key); + for (Key key : conditional.frontals()) { + if (!first) *ss << ","; + *ss << keyFormatter(key); first = false; } + if (conditional.nrParents() > 0) { + *ss << "|"; + bool first = true; + for (Key parent : conditional.parents()) { + if (!first) *ss << ","; + *ss << keyFormatter(parent); + first = false; + } + } + *ss << "):"; +} + +/* ************************************************************************* */ +std::string DiscreteConditional::markdown(const KeyFormatter& keyFormatter, + const Names& names) const { + stringstream ss; + ss << " *"; + streamSignature(*this, keyFormatter, &ss); + ss << "*\n" << std::endl; if (nrParents() == 0) { - // We have no parents, call factor method. - ss << ")*:\n" << std::endl; + // We have no parents, call factor method. ss << DecisionTreeFactor::markdown(keyFormatter, names); return ss.str(); } - // We have parents, continue signature and do custom print. + // Print out header. ss << "|"; - first = true; for (Key parent : parents()) { - if (!first) ss << ","; - ss << keyFormatter(parent); - first = false; - } - ss << ")*:\n" << std::endl; - - // Print out header and construct argument for `cartesianProduct`. - std::vector> pairs; - ss << "|"; - const_iterator it; - for(Key parent: parents()) { ss << "*" << keyFormatter(parent) << "*|"; - pairs.emplace_back(parent, cardinalities_.at(parent)); } - size_t n = 1; - for(Key key: frontals()) { - size_t k = cardinalities_.at(key); - pairs.emplace_back(key, k); - n *= k; - } - std::vector> slatnorf(pairs.rbegin(), - pairs.rend() - nrParents()); - const auto frontal_assignments = cartesianProduct(slatnorf); - for (const auto& a : frontal_assignments) { - for (it = beginFrontals(); it != endFrontals(); ++it) { + auto frontalAssignments = this->frontalAssignments(); + for (const auto& a : frontalAssignments) { + for (auto&& it = beginFrontals(); it != endFrontals(); ++it) { size_t index = a.at(*it); - ss << Translate(names, *it, index); + ss << DiscreteValues::Translate(names, *it, index); } ss << "|"; } @@ -350,19 +361,18 @@ std::string DiscreteConditional::markdown(const KeyFormatter& keyFormatter, // Print out separator with alignment hints. ss << "|"; + size_t n = frontalAssignments.size(); for (size_t j = 0; j < nrParents() + n; j++) ss << ":-:|"; ss << "\n"; // Print out all rows. - std::vector> rpairs(pairs.rbegin(), pairs.rend()); - const auto assignments = cartesianProduct(rpairs); size_t count = 0; - for (const auto& a : assignments) { + for (const auto& a : allAssignments()) { if (count == 0) { ss << "|"; - for (it = beginParents(); it != endParents(); ++it) { + for (auto&& it = beginParents(); it != endParents(); ++it) { size_t index = a.at(*it); - ss << Translate(names, *it, index) << "|"; + ss << DiscreteValues::Translate(names, *it, index) << "|"; } } ss << operator()(a) << "|"; @@ -371,6 +381,62 @@ std::string DiscreteConditional::markdown(const KeyFormatter& keyFormatter, } return ss.str(); } + +/* ************************************************************************ */ +string DiscreteConditional::html(const KeyFormatter& keyFormatter, + const Names& names) const { + stringstream ss; + ss << "
\n

"; + streamSignature(*this, keyFormatter, &ss); + ss << "

\n"; + if (nrParents() == 0) { + // We have no parents, call factor method. + ss << DecisionTreeFactor::html(keyFormatter, names); + return ss.str(); + } + + // Print out preamble. + ss << "\n \n"; + + // Print out header row. + ss << " "; + for (Key parent : parents()) { + ss << ""; + } + auto frontalAssignments = this->frontalAssignments(); + for (const auto& a : frontalAssignments) { + ss << ""; + } + ss << "\n"; + + // Finish header and start body. + ss << " \n \n"; + + // Output all rows, one per assignment: + size_t count = 0, n = frontalAssignments.size(); + for (const auto& a : allAssignments()) { + if (count == 0) { + ss << " "; + for (auto&& it = beginParents(); it != endParents(); ++it) { + size_t index = a.at(*it); + ss << ""; + } + } + ss << ""; // value + count = (count + 1) % n; + if (count == 0) ss << "\n"; + } + + // Finish up + ss << " \n
" << keyFormatter(parent) << ""; + for (auto&& it = beginFrontals(); it != endFrontals(); ++it) { + size_t index = a.at(*it); + ss << DiscreteValues::Translate(names, *it, index); + } + ss << "
" << DiscreteValues::Translate(names, *it, index) << "" << operator()(a) << "
\n
"; + return ss.str(); +} + /* ************************************************************************* */ } // namespace gtsam diff --git a/gtsam/discrete/DiscreteConditional.h b/gtsam/discrete/DiscreteConditional.h index 4c2e964fda..4a83ff83a0 100644 --- a/gtsam/discrete/DiscreteConditional.h +++ b/gtsam/discrete/DiscreteConditional.h @@ -21,10 +21,11 @@ #include #include #include -#include -#include +#include +#include #include +#include namespace gtsam { @@ -32,24 +33,24 @@ namespace gtsam { * Discrete Conditional Density * Derives from DecisionTreeFactor */ -class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, - public Conditional { - -public: +class GTSAM_EXPORT DiscreteConditional + : public DecisionTreeFactor, + public Conditional { + public: // typedefs needed to play nice with gtsam - typedef DiscreteConditional This; ///< Typedef to this class - typedef boost::shared_ptr shared_ptr; ///< shared_ptr to this class - typedef DecisionTreeFactor BaseFactor; ///< Typedef to our factor base class - typedef Conditional BaseConditional; ///< Typedef to our conditional base class + typedef DiscreteConditional This; ///< Typedef to this class + typedef boost::shared_ptr shared_ptr; ///< shared_ptr to this class + typedef DecisionTreeFactor BaseFactor; ///< Typedef to our factor base class + typedef Conditional + BaseConditional; ///< Typedef to our conditional base class - using Values = DiscreteValues; ///< backwards compatibility + using Values = DiscreteValues; ///< backwards compatibility /// @name Standard Constructors /// @{ /** default constructor needed for serialization */ - DiscreteConditional() { - } + DiscreteConditional() {} /** constructor from factor */ DiscreteConditional(size_t nFrontals, const DecisionTreeFactor& f); @@ -87,29 +88,33 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, /** construct P(X|Y)=P(X,Y)/P(Y) from P(X,Y) and P(Y) */ DiscreteConditional(const DecisionTreeFactor& joint, - const DecisionTreeFactor& marginal); + const DecisionTreeFactor& marginal); /** construct P(X|Y)=P(X,Y)/P(Y) from P(X,Y) and P(Y) */ DiscreteConditional(const DecisionTreeFactor& joint, - const DecisionTreeFactor& marginal, const Ordering& orderedKeys); + const DecisionTreeFactor& marginal, + const Ordering& orderedKeys); /** * Combine several conditional into a single one. - * The conditionals must be given in increasing order, meaning that the parents - * of any conditional may not include a conditional coming before it. - * @param firstConditional Iterator to the first conditional to combine, must dereference to a shared_ptr. - * @param lastConditional Iterator to after the last conditional to combine, must dereference to a shared_ptr. + * The conditionals must be given in increasing order, meaning that the + * parents of any conditional may not include a conditional coming before it. + * @param firstConditional Iterator to the first conditional to combine, must + * dereference to a shared_ptr. + * @param lastConditional Iterator to after the last conditional to combine, + * must dereference to a shared_ptr. * */ - template + template static shared_ptr Combine(ITERATOR firstConditional, - ITERATOR lastConditional); + ITERATOR lastConditional); /// @} /// @name Testable /// @{ /// GTSAM-style print - void print(const std::string& s = "Discrete Conditional: ", + void print( + const std::string& s = "Discrete Conditional: ", const KeyFormatter& formatter = DefaultKeyFormatter) const override; /// GTSAM-style equals @@ -161,7 +166,6 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, */ size_t sample(const DiscreteValues& parentsValues) const; - /// Single parent version. size_t sample(size_t parent_value) const; @@ -178,6 +182,12 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, /// sample in place, stores result in partial solution void sampleInPlace(DiscreteValues* parentsValues) const; + /// Return all assignments for frontal variables. + std::vector frontalAssignments() const; + + /// Return all assignments for frontal *and* parent variables. + std::vector allAssignments() const; + /// @} /// @name Wrapper support /// @{ @@ -186,15 +196,20 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor, std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, const Names& names = {}) const override; + /// Render as html table. + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const override; + /// @} }; // DiscreteConditional // traits -template<> struct traits : public Testable {}; +template <> +struct traits : public Testable {}; /* ************************************************************************* */ -template +template DiscreteConditional::shared_ptr DiscreteConditional::Combine( ITERATOR firstConditional, ITERATOR lastConditional) { // TODO: check for being a clique @@ -203,7 +218,7 @@ DiscreteConditional::shared_ptr DiscreteConditional::Combine( size_t nrFrontals = 0; DecisionTreeFactor product; for (ITERATOR it = firstConditional; it != lastConditional; - ++it, ++nrFrontals) { + ++it, ++nrFrontals) { DiscreteConditional::shared_ptr c = *it; DecisionTreeFactor::shared_ptr factor = c->toFactor(); product = (*factor) * product; @@ -212,5 +227,4 @@ DiscreteConditional::shared_ptr DiscreteConditional::Combine( return boost::make_shared(nrFrontals, product); } -} // gtsam - +} // namespace gtsam diff --git a/gtsam/discrete/DiscreteFactor.cpp b/gtsam/discrete/DiscreteFactor.cpp index 1a12ef405a..0cf7f2a5e8 100644 --- a/gtsam/discrete/DiscreteFactor.cpp +++ b/gtsam/discrete/DiscreteFactor.cpp @@ -25,14 +25,4 @@ using namespace std; namespace gtsam { -string DiscreteFactor::Translate(const Names& names, Key key, size_t index) { - if (names.empty()) { - stringstream ss; - ss << index; - return ss.str(); - } else { - return names.at(key)[index]; - } -} - } // namespace gtsam diff --git a/gtsam/discrete/DiscreteFactor.h b/gtsam/discrete/DiscreteFactor.h index e30c0a6fec..8f39fbc23f 100644 --- a/gtsam/discrete/DiscreteFactor.h +++ b/gtsam/discrete/DiscreteFactor.h @@ -22,6 +22,7 @@ #include #include +#include namespace gtsam { class DecisionTreeFactor; @@ -90,14 +91,11 @@ class GTSAM_EXPORT DiscreteFactor: public Factor { /// @{ /// Translation table from values to strings. - using Names = std::map>; - - /// Translate an integer index value for given key to a string. - static std::string Translate(const Names& names, Key key, size_t index); + using Names = DiscreteValues::Names; /** * @brief Render as markdown table - * + * * @param keyFormatter GTSAM-style Key formatter. * @param names optional, category names corresponding to choices. * @return std::string a markdown string. @@ -106,6 +104,17 @@ class GTSAM_EXPORT DiscreteFactor: public Factor { const KeyFormatter& keyFormatter = DefaultKeyFormatter, const Names& names = {}) const = 0; + /** + * @brief Render as html table + * + * @param keyFormatter GTSAM-style Key formatter. + * @param names optional, category names corresponding to choices. + * @return std::string a html string. + */ + virtual std::string html( + const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const = 0; + /// @} }; // DiscreteFactor diff --git a/gtsam/discrete/DiscreteFactorGraph.cpp b/gtsam/discrete/DiscreteFactorGraph.cpp index be046d2902..c1248c60b9 100644 --- a/gtsam/discrete/DiscreteFactorGraph.cpp +++ b/gtsam/discrete/DiscreteFactorGraph.cpp @@ -131,7 +131,7 @@ namespace gtsam { return std::make_pair(cond, sum); } - /* ************************************************************************* */ + /* ************************************************************************ */ string DiscreteFactorGraph::markdown( const KeyFormatter& keyFormatter, const DiscreteFactor::Names& names) const { @@ -145,5 +145,18 @@ namespace gtsam { return ss.str(); } - /* ************************************************************************* */ + /* ************************************************************************ */ + string DiscreteFactorGraph::html(const KeyFormatter& keyFormatter, + const DiscreteFactor::Names& names) const { + using std::endl; + std::stringstream ss; + ss << "

DiscreteFactorGraph of size " << size() << "

"; + for (size_t i = 0; i < factors_.size(); i++) { + ss << "

factor " << i << ":

"; + ss << factors_[i]->html(keyFormatter, names) << endl; + } + return ss.str(); + } + + /* ************************************************************************ */ } // namespace gtsam diff --git a/gtsam/discrete/DiscreteFactorGraph.h b/gtsam/discrete/DiscreteFactorGraph.h index 9aa04d6497..08c3d893d9 100644 --- a/gtsam/discrete/DiscreteFactorGraph.h +++ b/gtsam/discrete/DiscreteFactorGraph.h @@ -22,18 +22,17 @@ #include #include #include -#include #include #include #include +#include #include namespace gtsam { // Forward declarations class DiscreteFactorGraph; -class DiscreteFactor; class DiscreteConditional; class DiscreteBayesNet; class DiscreteEliminationTree; @@ -144,8 +143,8 @@ public EliminateableFactorGraph { /// @{ /** - * @brief Render as markdown table - * + * @brief Render as markdown tables + * * @param keyFormatter GTSAM-style Key formatter. * @param names optional, a map from Key to category names. * @return std::string a (potentially long) markdown string. @@ -153,6 +152,16 @@ public EliminateableFactorGraph { std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, const DiscreteFactor::Names& names = {}) const; + /** + * @brief Render as html tables + * + * @param keyFormatter GTSAM-style Key formatter. + * @param names optional, a map from Key to category names. + * @return std::string a (potentially long) html string. + */ + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const DiscreteFactor::Names& names = {}) const; + /// @} }; // \ DiscreteFactorGraph diff --git a/gtsam/discrete/DiscreteValues.cpp b/gtsam/discrete/DiscreteValues.cpp new file mode 100644 index 0000000000..5d0c8dd3d5 --- /dev/null +++ b/gtsam/discrete/DiscreteValues.cpp @@ -0,0 +1,97 @@ +/* ---------------------------------------------------------------------------- + + * 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 DiscreteValues.cpp + * @date January, 2022 + * @author Frank Dellaert + */ + +#include + +#include + +using std::cout; +using std::endl; +using std::string; +using std::stringstream; + +namespace gtsam { + +void DiscreteValues::print(const string& s, + const KeyFormatter& keyFormatter) const { + cout << s << ": "; + for (auto&& kv : *this) + cout << "(" << keyFormatter(kv.first) << ", " << kv.second << ")"; + cout << endl; +} + +string DiscreteValues::Translate(const Names& names, Key key, size_t index) { + if (names.empty()) { + stringstream ss; + ss << index; + return ss.str(); + } else { + return names.at(key)[index]; + } +} + +string DiscreteValues::markdown(const KeyFormatter& keyFormatter, + const Names& names) const { + stringstream ss; + + // Print out header and separator with alignment hints. + ss << "|Variable|value|\n|:-:|:-:|\n"; + + // Print out all rows. + for (const auto& kv : *this) { + ss << "|" << keyFormatter(kv.first) << "|" + << Translate(names, kv.first, kv.second) << "|\n"; + } + + return ss.str(); +} + +string DiscreteValues::html(const KeyFormatter& keyFormatter, + const Names& names) const { + stringstream ss; + + // Print out preamble. + ss << "
\n\n \n"; + + // Print out header row. + ss << " \n"; + + // Finish header and start body. + ss << " \n \n"; + + // Print out all rows. + for (const auto& kv : *this) { + ss << " "; + ss << ""; + ss << "\n"; + } + ss << " \n
Variablevalue
" << keyFormatter(kv.first) << "" + << Translate(names, kv.first, kv.second) << "
\n
"; + return ss.str(); +} + +string markdown(const DiscreteValues& values, const KeyFormatter& keyFormatter, + const DiscreteValues::Names& names) { + return values.markdown(keyFormatter, names); +} + +string html(const DiscreteValues& values, const KeyFormatter& keyFormatter, + const DiscreteValues::Names& names) { + return values.html(keyFormatter, names); +} + +} // namespace gtsam diff --git a/gtsam/discrete/DiscreteValues.h b/gtsam/discrete/DiscreteValues.h index 2d9c8d3cfb..81997a7831 100644 --- a/gtsam/discrete/DiscreteValues.h +++ b/gtsam/discrete/DiscreteValues.h @@ -18,8 +18,13 @@ #pragma once #include +#include #include +#include +#include +#include + namespace gtsam { /** A map from keys to values @@ -34,25 +39,68 @@ namespace gtsam { */ class DiscreteValues : public Assignment { public: - using Assignment::Assignment; // all constructors + using Base = Assignment; // base class + + using Assignment::Assignment; // all constructors // Define the implicit default constructor. DiscreteValues() = default; // Construct from assignment. - DiscreteValues(const Assignment& a) : Assignment(a) {} + explicit DiscreteValues(const Base& a) : Base(a) {} void print(const std::string& s = "", - const KeyFormatter& keyFormatter = DefaultKeyFormatter) const { - std::cout << s << ": "; - for (const typename Assignment::value_type& keyValue : *this) - std::cout << "(" << keyFormatter(keyValue.first) << ", " - << keyValue.second << ")"; - std::cout << std::endl; + const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; + + static std::vector CartesianProduct( + const DiscreteKeys& keys) { + return Base::CartesianProduct(keys); } + + /// @name Wrapper support + /// @{ + + /// Translation table from values to strings. + using Names = std::map>; + + /// Translate an integer index value for given key to a string. + static std::string Translate(const Names& names, Key key, size_t index); + + /** + * @brief Output as a markdown table. + * + * @param keyFormatter function that formats keys. + * @param names translation table for values. + * @return string markdown output. + */ + std::string markdown(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const; + + /** + * @brief Output as a html table. + * + * @param keyFormatter function that formats keys. + * @param names translation table for values. + * @return string html output. + */ + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const; + + /// @} }; +/// Free version of markdown. +std::string markdown(const DiscreteValues& values, + const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const DiscreteValues::Names& names = {}); + +/// Free version of html. +std::string html(const DiscreteValues& values, + const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const DiscreteValues::Names& names = {}); + // traits -template<> struct traits : public Testable {}; +template <> +struct traits : public Testable {}; } // namespace gtsam diff --git a/gtsam/discrete/discrete.i b/gtsam/discrete/discrete.i index d17401e44f..218b790e88 100644 --- a/gtsam/discrete/discrete.i +++ b/gtsam/discrete/discrete.i @@ -17,6 +17,18 @@ class DiscreteKeys { }; // DiscreteValues is added in specializations/discrete.h as a std::map +string markdown( + const gtsam::DiscreteValues& values, + const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter); +string markdown(const gtsam::DiscreteValues& values, + const gtsam::KeyFormatter& keyFormatter, + std::map> names); +string html( + const gtsam::DiscreteValues& values, + const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter); +string html(const gtsam::DiscreteValues& values, + const gtsam::KeyFormatter& keyFormatter, + std::map> names); #include class DiscreteFactor { @@ -54,6 +66,10 @@ virtual class DecisionTreeFactor : gtsam::DiscreteFactor { gtsam::DefaultKeyFormatter) const; string markdown(const gtsam::KeyFormatter& keyFormatter, std::map> names) const; + string html(const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; + string html(const gtsam::KeyFormatter& keyFormatter, + std::map> names) const; }; #include @@ -93,6 +109,10 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor { gtsam::DefaultKeyFormatter) const; string markdown(const gtsam::KeyFormatter& keyFormatter, std::map> names) const; + string html(const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; + string html(const gtsam::KeyFormatter& keyFormatter, + std::map> names) const; }; #include @@ -136,6 +156,10 @@ class DiscreteBayesNet { gtsam::DefaultKeyFormatter) const; string markdown(const gtsam::KeyFormatter& keyFormatter, std::map> names) const; + string html(const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; + string html(const gtsam::KeyFormatter& keyFormatter, + std::map> names) const; }; #include @@ -172,6 +196,10 @@ class DiscreteBayesTree { gtsam::DefaultKeyFormatter) const; string markdown(const gtsam::KeyFormatter& keyFormatter, std::map> names) const; + string html(const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; + string html(const gtsam::KeyFormatter& keyFormatter, + std::map> names) const; }; #include @@ -221,6 +249,10 @@ class DiscreteFactorGraph { gtsam::DefaultKeyFormatter) const; string markdown(const gtsam::KeyFormatter& keyFormatter, std::map> names) const; + string html(const gtsam::KeyFormatter& keyFormatter = + gtsam::DefaultKeyFormatter) const; + string html(const gtsam::KeyFormatter& keyFormatter, + std::map> names) const; }; } // namespace gtsam diff --git a/gtsam/discrete/tests/testDecisionTreeFactor.cpp b/gtsam/discrete/tests/testDecisionTreeFactor.cpp index 716a77127c..594134edf7 100644 --- a/gtsam/discrete/tests/testDecisionTreeFactor.cpp +++ b/gtsam/discrete/tests/testDecisionTreeFactor.cpp @@ -154,6 +154,34 @@ TEST(DecisionTreeFactor, markdownWithValueFormatter) { EXPECT(actual == expected); } +/* ************************************************************************* */ +// Check html representation with a value formatter. +TEST(DecisionTreeFactor, htmlWithValueFormatter) { + DiscreteKey A(12, 3), B(5, 2); + DecisionTreeFactor f(A & B, "1 2 3 4 5 6"); + string expected = + "
\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
ABvalue
Zero-1
Zero+2
One-3
One+4
Two-5
Two+6
\n" + "
"; + auto keyFormatter = [](Key key) { return key == 12 ? "A" : "B"; }; + DecisionTreeFactor::Names names{{12, {"Zero", "One", "Two"}}, + {5, {"-", "+"}}}; + string actual = f.html(keyFormatter, names); + EXPECT(actual == expected); +} + /* ************************************************************************* */ int main() { TestResult tr; diff --git a/gtsam/discrete/tests/testDiscreteBayesNet.cpp b/gtsam/discrete/tests/testDiscreteBayesNet.cpp index 0686b3920c..0ba53c69ab 100644 --- a/gtsam/discrete/tests/testDiscreteBayesNet.cpp +++ b/gtsam/discrete/tests/testDiscreteBayesNet.cpp @@ -182,13 +182,13 @@ TEST(DiscreteBayesNet, markdown) { string expected = "`DiscreteBayesNet` of size 2\n" "\n" - " *P(Asia)*:\n\n" + " *P(Asia):*\n\n" "|Asia|value|\n" "|:-:|:-:|\n" "|0|0.99|\n" "|1|0.01|\n" "\n" - " *P(Smoking|Asia)*:\n\n" + " *P(Smoking|Asia):*\n\n" "|*Asia*|0|1|\n" "|:-:|:-:|:-:|\n" "|0|0.8|0.2|\n" diff --git a/gtsam/discrete/tests/testDiscreteBayesTree.cpp b/gtsam/discrete/tests/testDiscreteBayesTree.cpp index edb5ea46c6..26356be3d8 100644 --- a/gtsam/discrete/tests/testDiscreteBayesTree.cpp +++ b/gtsam/discrete/tests/testDiscreteBayesTree.cpp @@ -101,10 +101,10 @@ TEST(DiscreteBayesTree, ThinTree) { auto R = self.bayesTree->roots().front(); // Check whether BN and BT give the same answer on all configurations - auto allPosbValues = - cartesianProduct(keys[0] & keys[1] & keys[2] & keys[3] & keys[4] & - keys[5] & keys[6] & keys[7] & keys[8] & keys[9] & - keys[10] & keys[11] & keys[12] & keys[13] & keys[14]); + auto allPosbValues = DiscreteValues::CartesianProduct( + keys[0] & keys[1] & keys[2] & keys[3] & keys[4] & keys[5] & keys[6] & + keys[7] & keys[8] & keys[9] & keys[10] & keys[11] & keys[12] & keys[13] & + keys[14]); for (size_t i = 0; i < allPosbValues.size(); ++i) { DiscreteValues x = allPosbValues[i]; double expected = self.bayesNet.evaluate(x); diff --git a/gtsam/discrete/tests/testDiscreteConditional.cpp b/gtsam/discrete/tests/testDiscreteConditional.cpp index 6d2af3cff9..3fb67a615c 100644 --- a/gtsam/discrete/tests/testDiscreteConditional.cpp +++ b/gtsam/discrete/tests/testDiscreteConditional.cpp @@ -125,7 +125,7 @@ TEST(DiscreteConditional, markdown_prior) { DiscreteKey A(Symbol('x', 1), 3); DiscreteConditional conditional(A % "1/2/2"); string expected = - " *P(x1)*:\n\n" + " *P(x1):*\n\n" "|x1|value|\n" "|:-:|:-:|\n" "|0|0.2|\n" @@ -142,7 +142,7 @@ TEST(DiscreteConditional, markdown_prior_names) { DiscreteKey A(x1, 3); DiscreteConditional conditional(A % "1/2/2"); string expected = - " *P(x1)*:\n\n" + " *P(x1):*\n\n" "|x1|value|\n" "|:-:|:-:|\n" "|A0|0.2|\n" @@ -160,7 +160,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\n" + " *P(a1|b1):*\n\n" "|*b1*|0|1|2|\n" "|:-:|:-:|:-:|:-:|\n" "|0|0.02|0.88|0.1|\n" @@ -178,7 +178,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\n" + " *P(A|B,C):*\n\n" "|*B*|*C*|T|F|\n" "|:-:|:-:|:-:|:-:|\n" "|-|Zero|0|1|\n" @@ -195,6 +195,36 @@ TEST(DiscreteConditional, markdown) { EXPECT(actual == expected); } +/* ************************************************************************* */ +// Check html representation looks as expected, two parents + names. +TEST(DiscreteConditional, html) { + 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 = + "
\n" + "

P(A|B,C):

\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
BCTF
-Zero01
-One0.250.75
-Two0.50.5
+Zero0.750.25
+One01
+Two10
\n" + "
"; + vector keyNames{"C", "B", "A"}; + auto formatter = [keyNames](Key key) { return keyNames[key]; }; + DecisionTreeFactor::Names names{ + {0, {"Zero", "One", "Two"}}, {1, {"-", "+"}}, {2, {"T", "F"}}}; + string actual = conditional.html(formatter, names); + EXPECT(actual == expected); +} + /* ************************************************************************* */ int main() { TestResult tr; diff --git a/gtsam/discrete/tests/testDiscreteMarginals.cpp b/gtsam/discrete/tests/testDiscreteMarginals.cpp index e75016b683..3208f81c53 100644 --- a/gtsam/discrete/tests/testDiscreteMarginals.cpp +++ b/gtsam/discrete/tests/testDiscreteMarginals.cpp @@ -164,8 +164,8 @@ TEST_UNSAFE(DiscreteMarginals, truss2) { graph.add(key[2] & key[3] & key[4], "1 2 3 4 5 6 7 8"); // Calculate the marginals by brute force - auto allPosbValues = - cartesianProduct(key[0] & key[1] & key[2] & key[3] & key[4]); + auto allPosbValues = DiscreteValues::CartesianProduct( + key[0] & key[1] & key[2] & key[3] & key[4]); Vector T = Z_5x1, F = Z_5x1; for (size_t i = 0; i < allPosbValues.size(); ++i) { DiscreteValues x = allPosbValues[i]; diff --git a/gtsam/discrete/tests/testDiscreteValues.cpp b/gtsam/discrete/tests/testDiscreteValues.cpp new file mode 100644 index 0000000000..c8a1fa1680 --- /dev/null +++ b/gtsam/discrete/tests/testDiscreteValues.cpp @@ -0,0 +1,76 @@ +/* ---------------------------------------------------------------------------- + + * 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 + + * -------------------------------------------------------------------------- */ + +/* + * testDiscreteValues.cpp + * + * @date Jan, 2022 + * @author Frank Dellaert + */ + +#include +#include +#include +#include + +#include +using namespace boost::assign; + +using namespace std; +using namespace gtsam; + +/* ************************************************************************* */ +// Check markdown representation with a value formatter. +TEST(DiscreteValues, markdownWithValueFormatter) { + DiscreteValues values; + values[12] = 1; // A + values[5] = 0; // B + string expected = + "|Variable|value|\n" + "|:-:|:-:|\n" + "|B|-|\n" + "|A|One|\n"; + auto keyFormatter = [](Key key) { return key == 12 ? "A" : "B"; }; + DiscreteValues::Names names{{12, {"Zero", "One", "Two"}}, {5, {"-", "+"}}}; + string actual = values.markdown(keyFormatter, names); + EXPECT(actual == expected); +} + +/* ************************************************************************* */ +// Check html representation with a value formatter. +TEST(DiscreteValues, htmlWithValueFormatter) { + DiscreteValues values; + values[12] = 1; // A + values[5] = 0; // B + string expected = + "
\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
Variablevalue
B-
AOne
\n" + "
"; + auto keyFormatter = [](Key key) { return key == 12 ? "A" : "B"; }; + DiscreteValues::Names names{{12, {"Zero", "One", "Two"}}, {5, {"-", "+"}}}; + string actual = values.html(keyFormatter, names); + EXPECT(actual == expected); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ diff --git a/gtsam_unstable/discrete/CSP.cpp b/gtsam_unstable/discrete/CSP.cpp index 283c992f13..e204a67796 100644 --- a/gtsam_unstable/discrete/CSP.cpp +++ b/gtsam_unstable/discrete/CSP.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include diff --git a/gtsam_unstable/discrete/Constraint.h b/gtsam_unstable/discrete/Constraint.h index 85748f0546..4ee7b85eb4 100644 --- a/gtsam_unstable/discrete/Constraint.h +++ b/gtsam_unstable/discrete/Constraint.h @@ -91,6 +91,12 @@ class GTSAM_EXPORT Constraint : public DiscreteFactor { return (boost::format("`Constraint` on %1% variables\n") % (size())).str(); } + /// Render as html table. + std::string html(const KeyFormatter& keyFormatter = DefaultKeyFormatter, + const Names& names = {}) const override { + return (boost::format("

Constraint on %1% variables

") % (size())).str(); + } + /// @} }; // DiscreteFactor diff --git a/gtsam_unstable/discrete/Scheduler.h b/gtsam_unstable/discrete/Scheduler.h index 7559cdea6b..a97368bb25 100644 --- a/gtsam_unstable/discrete/Scheduler.h +++ b/gtsam_unstable/discrete/Scheduler.h @@ -8,6 +8,7 @@ #pragma once #include +#include namespace gtsam { diff --git a/gtsam_unstable/discrete/tests/testLoopyBelief.cpp b/gtsam_unstable/discrete/tests/testLoopyBelief.cpp index 6561949b14..eac0d834e6 100644 --- a/gtsam_unstable/discrete/tests/testLoopyBelief.cpp +++ b/gtsam_unstable/discrete/tests/testLoopyBelief.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/python/gtsam/tests/test_DiscreteConditional.py b/python/gtsam/tests/test_DiscreteConditional.py index 86bc303a9a..0ae66c2d40 100644 --- a/python/gtsam/tests/test_DiscreteConditional.py +++ b/python/gtsam/tests/test_DiscreteConditional.py @@ -49,7 +49,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\n" \ + " *P(A|B,C):*\n\n" \ "|*B*|*C*|0|1|\n" \ "|:-:|:-:|:-:|:-:|\n" \ "|0|0|0|1|\n" \ diff --git a/python/gtsam/tests/test_DiscretePrior.py b/python/gtsam/tests/test_DiscretePrior.py index 5bf6a8d196..2c923589ce 100644 --- a/python/gtsam/tests/test_DiscretePrior.py +++ b/python/gtsam/tests/test_DiscretePrior.py @@ -51,7 +51,7 @@ def test_markdown(self): """Test the _repr_markdown_ method.""" prior = DiscretePrior(X, "2/3") - expected = " *P(0)*:\n\n" \ + expected = " *P(0):*\n\n" \ "|0|value|\n" \ "|:-:|:-:|\n" \ "|0|0.4|\n" \