commit
						232fa02b19
					
				|  | @ -24,6 +24,7 @@ | |||
| #include <gtsam/hybrid/GaussianMixtureFactor.h> | ||||
| #include <gtsam/hybrid/HybridValues.h> | ||||
| #include <gtsam/inference/Conditional-inst.h> | ||||
| #include <gtsam/linear/GaussianBayesNet.h> | ||||
| #include <gtsam/linear/GaussianFactorGraph.h> | ||||
| 
 | ||||
| namespace gtsam { | ||||
|  | @ -86,7 +87,22 @@ GaussianFactorGraphTree GaussianMixture::add( | |||
| 
 | ||||
| /* *******************************************************************************/ | ||||
| GaussianFactorGraphTree GaussianMixture::asGaussianFactorGraphTree() const { | ||||
|   auto wrap = [](const GaussianConditional::shared_ptr &gc) { | ||||
|   auto wrap = [this](const GaussianConditional::shared_ptr &gc) { | ||||
|     // First check if conditional has not been pruned
 | ||||
|     if (gc) { | ||||
|       const double Cgm_Kgcm = | ||||
|           this->logConstant_ - gc->logNormalizationConstant(); | ||||
|       // If there is a difference in the covariances, we need to account for
 | ||||
|       // that since the error is dependent on the mode.
 | ||||
|       if (Cgm_Kgcm > 0.0) { | ||||
|         // We add a constant factor which will be used when computing
 | ||||
|         // the probability of the discrete variables.
 | ||||
|         Vector c(1); | ||||
|         c << std::sqrt(2.0 * Cgm_Kgcm); | ||||
|         auto constantFactor = std::make_shared<JacobianFactor>(c); | ||||
|         return GaussianFactorGraph{gc, constantFactor}; | ||||
|       } | ||||
|     } | ||||
|     return GaussianFactorGraph{gc}; | ||||
|   }; | ||||
|   return {conditionals_, wrap}; | ||||
|  | @ -145,6 +161,8 @@ void GaussianMixture::print(const std::string &s, | |||
|     std::cout << "(" << formatter(dk.first) << ", " << dk.second << "), "; | ||||
|   } | ||||
|   std::cout << "\n"; | ||||
|   std::cout << " logNormalizationConstant: " << logConstant_ << "\n" | ||||
|             << std::endl; | ||||
|   conditionals_.print( | ||||
|       "", [&](Key k) { return formatter(k); }, | ||||
|       [&](const GaussianConditional::shared_ptr &gf) -> std::string { | ||||
|  | @ -312,12 +330,28 @@ AlgebraicDecisionTree<Key> GaussianMixture::logProbability( | |||
|   return DecisionTree<Key, double>(conditionals_, probFunc); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| double GaussianMixture::conditionalError( | ||||
|     const GaussianConditional::shared_ptr &conditional, | ||||
|     const VectorValues &continuousValues) const { | ||||
|   // Check if valid pointer
 | ||||
|   if (conditional) { | ||||
|     return conditional->error(continuousValues) +  //
 | ||||
|            logConstant_ - conditional->logNormalizationConstant(); | ||||
|   } else { | ||||
|     // If not valid, pointer, it means this conditional was pruned,
 | ||||
|     // so we return maximum error.
 | ||||
|     // This way the negative exponential will give
 | ||||
|     // a probability value close to 0.0.
 | ||||
|     return std::numeric_limits<double>::max(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* *******************************************************************************/ | ||||
| AlgebraicDecisionTree<Key> GaussianMixture::errorTree( | ||||
|     const VectorValues &continuousValues) const { | ||||
|   auto errorFunc = [&](const GaussianConditional::shared_ptr &conditional) { | ||||
|     return conditional->error(continuousValues) +  //
 | ||||
|            logConstant_ - conditional->logNormalizationConstant(); | ||||
|     return conditionalError(conditional, continuousValues); | ||||
|   }; | ||||
|   DecisionTree<Key, double> error_tree(conditionals_, errorFunc); | ||||
|   return error_tree; | ||||
|  | @ -327,8 +361,7 @@ AlgebraicDecisionTree<Key> GaussianMixture::errorTree( | |||
| double GaussianMixture::error(const HybridValues &values) const { | ||||
|   // Directly index to get the conditional, no need to build the whole tree.
 | ||||
|   auto conditional = conditionals_(values.discrete()); | ||||
|   return conditional->error(values.continuous()) +  //
 | ||||
|          logConstant_ - conditional->logNormalizationConstant(); | ||||
|   return conditionalError(conditional, values.continuous()); | ||||
| } | ||||
| 
 | ||||
| /* *******************************************************************************/ | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ class GTSAM_EXPORT GaussianMixture | |||
|   double logConstant_;         ///< log of the normalization constant.
 | ||||
| 
 | ||||
|   /**
 | ||||
|    * @brief Convert a DecisionTree of factors into | ||||
|    * @brief Convert a GaussianMixture of conditionals into | ||||
|    * a DecisionTree of Gaussian factor graphs. | ||||
|    */ | ||||
|   GaussianFactorGraphTree asGaussianFactorGraphTree() const; | ||||
|  | @ -256,6 +256,10 @@ class GTSAM_EXPORT GaussianMixture | |||
|   /// Check whether `given` has values for all frontal keys.
 | ||||
|   bool allFrontalsGiven(const VectorValues &given) const; | ||||
| 
 | ||||
|   /// Helper method to compute the error of a conditional.
 | ||||
|   double conditionalError(const GaussianConditional::shared_ptr &conditional, | ||||
|                           const VectorValues &continuousValues) const; | ||||
| 
 | ||||
| #ifdef GTSAM_ENABLE_BOOST_SERIALIZATION | ||||
|   /** Serialization function */ | ||||
|   friend class boost::serialization::access; | ||||
|  |  | |||
|  | @ -54,7 +54,9 @@ bool GaussianMixtureFactor::equals(const HybridFactor &lf, double tol) const { | |||
| /* *******************************************************************************/ | ||||
| void GaussianMixtureFactor::print(const std::string &s, | ||||
|                                   const KeyFormatter &formatter) const { | ||||
|   HybridFactor::print(s, formatter); | ||||
|   std::cout << (s.empty() ? "" : s + "\n"); | ||||
|   std::cout << "GaussianMixtureFactor" << std::endl; | ||||
|   HybridFactor::print("", formatter); | ||||
|   std::cout << "{\n"; | ||||
|   if (factors_.empty()) { | ||||
|     std::cout << "  empty" << std::endl; | ||||
|  | @ -64,7 +66,7 @@ void GaussianMixtureFactor::print(const std::string &s, | |||
|         [&](const sharedFactor &gf) -> std::string { | ||||
|           RedirectCout rd; | ||||
|           std::cout << ":\n"; | ||||
|           if (gf && !gf->empty()) { | ||||
|           if (gf) { | ||||
|             gf->print("", formatter); | ||||
|             return rd.str(); | ||||
|           } else { | ||||
|  | @ -117,6 +119,5 @@ double GaussianMixtureFactor::error(const HybridValues &values) const { | |||
|   const sharedFactor gf = factors_(values.discrete()); | ||||
|   return gf->error(values.continuous()); | ||||
| } | ||||
| /* *******************************************************************************/ | ||||
| 
 | ||||
| }  // namespace gtsam
 | ||||
|  |  | |||
|  | @ -80,8 +80,8 @@ class GTSAM_EXPORT GaussianMixtureFactor : public HybridFactor { | |||
|    * @param continuousKeys A vector of keys representing continuous variables. | ||||
|    * @param discreteKeys A vector of keys representing discrete variables and | ||||
|    * their cardinalities. | ||||
|    * @param factors The decision tree of Gaussian factors stored as the mixture | ||||
|    * density. | ||||
|    * @param factors The decision tree of Gaussian factors stored | ||||
|    * as the mixture density. | ||||
|    */ | ||||
|   GaussianMixtureFactor(const KeyVector &continuousKeys, | ||||
|                         const DiscreteKeys &discreteKeys, | ||||
|  | @ -107,9 +107,8 @@ class GTSAM_EXPORT GaussianMixtureFactor : public HybridFactor { | |||
| 
 | ||||
|   bool equals(const HybridFactor &lf, double tol = 1e-9) const override; | ||||
| 
 | ||||
|   void print( | ||||
|       const std::string &s = "GaussianMixtureFactor\n", | ||||
|       const KeyFormatter &formatter = DefaultKeyFormatter) const override; | ||||
|   void print(const std::string &s = "", const KeyFormatter &formatter = | ||||
|                                             DefaultKeyFormatter) const override; | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Standard API
 | ||||
|  |  | |||
|  | @ -220,15 +220,16 @@ GaussianBayesNet HybridBayesNet::choose( | |||
| /* ************************************************************************* */ | ||||
| HybridValues HybridBayesNet::optimize() const { | ||||
|   // Collect all the discrete factors to compute MPE
 | ||||
|   DiscreteBayesNet discrete_bn; | ||||
|   DiscreteFactorGraph discrete_fg; | ||||
| 
 | ||||
|   for (auto &&conditional : *this) { | ||||
|     if (conditional->isDiscrete()) { | ||||
|       discrete_bn.push_back(conditional->asDiscrete()); | ||||
|       discrete_fg.push_back(conditional->asDiscrete()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Solve for the MPE
 | ||||
|   DiscreteValues mpe = DiscreteFactorGraph(discrete_bn).optimize(); | ||||
|   DiscreteValues mpe = discrete_fg.optimize(); | ||||
| 
 | ||||
|   // Given the MPE, compute the optimal continuous values.
 | ||||
|   return HybridValues(optimize(mpe), mpe); | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ class GTSAM_EXPORT HybridConditional | |||
|       public Conditional<HybridFactor, HybridConditional> { | ||||
|  public: | ||||
|   // typedefs needed to play nice with gtsam
 | ||||
|   typedef HybridConditional This;              ///< Typedef to this class
 | ||||
|   typedef HybridConditional This;            ///< Typedef to this class
 | ||||
|   typedef std::shared_ptr<This> shared_ptr;  ///< shared_ptr to this class
 | ||||
|   typedef HybridFactor BaseFactor;  ///< Typedef to our factor base class
 | ||||
|   typedef Conditional<BaseFactor, This> | ||||
|  | @ -185,7 +185,7 @@ class GTSAM_EXPORT HybridConditional | |||
|    * Return the log normalization constant. | ||||
|    * Note this is 0.0 for discrete and hybrid conditionals, but depends | ||||
|    * on the continuous parameters for Gaussian conditionals. | ||||
|    */  | ||||
|    */ | ||||
|   double logNormalizationConstant() const override; | ||||
| 
 | ||||
|   /// Return the probability (or density) of the underlying conditional.
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
|  *  @file HybridFactor.h | ||||
|  *  @date Mar 11, 2022 | ||||
|  *  @author Fan Jiang | ||||
|  *  @author Varun Agrawal | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
|  |  | |||
|  | @ -49,15 +49,6 @@ KeySet HybridFactorGraph::discreteKeySet() const { | |||
|   return keys; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| std::unordered_map<Key, DiscreteKey> HybridFactorGraph::discreteKeyMap() const { | ||||
|   std::unordered_map<Key, DiscreteKey> result; | ||||
|   for (const DiscreteKey& k : discreteKeys()) { | ||||
|     result[k.first] = k; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| const KeySet HybridFactorGraph::continuousKeySet() const { | ||||
|   KeySet keys; | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ using SharedFactor = std::shared_ptr<Factor>; | |||
| class GTSAM_EXPORT HybridFactorGraph : public FactorGraph<Factor> { | ||||
|  public: | ||||
|   using Base = FactorGraph<Factor>; | ||||
|   using This = HybridFactorGraph;              ///< this class
 | ||||
|   using This = HybridFactorGraph;            ///< this class
 | ||||
|   using shared_ptr = std::shared_ptr<This>;  ///< shared_ptr to This
 | ||||
| 
 | ||||
|   using Values = gtsam::Values;  ///< backwards compatibility
 | ||||
|  | @ -66,12 +66,9 @@ class GTSAM_EXPORT HybridFactorGraph : public FactorGraph<Factor> { | |||
|   /// Get all the discrete keys in the factor graph.
 | ||||
|   std::set<DiscreteKey> discreteKeys() const; | ||||
| 
 | ||||
|   /// Get all the discrete keys in the factor graph, as a set.
 | ||||
|   /// Get all the discrete keys in the factor graph, as a set of Keys.
 | ||||
|   KeySet discreteKeySet() const; | ||||
| 
 | ||||
|   /// Get a map from Key to corresponding DiscreteKey.
 | ||||
|   std::unordered_map<Key, DiscreteKey> discreteKeyMap() const; | ||||
| 
 | ||||
|   /// Get all the continuous keys in the factor graph.
 | ||||
|   const KeySet continuousKeySet() const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -97,29 +97,27 @@ void HybridGaussianFactorGraph::printErrors( | |||
|         std::cout << "nullptr" | ||||
|                   << "\n"; | ||||
|       } else { | ||||
|         factor->print(ss.str(), keyFormatter); | ||||
|         std::cout << "error = "; | ||||
|         gmf->errorTree(values.continuous()).print("", keyFormatter); | ||||
|         std::cout << std::endl; | ||||
|         gmf->operator()(values.discrete())->print(ss.str(), keyFormatter); | ||||
|         std::cout << "error = " << gmf->error(values) << std::endl; | ||||
|       } | ||||
|     } else if (auto hc = std::dynamic_pointer_cast<HybridConditional>(factor)) { | ||||
|       if (factor == nullptr) { | ||||
|         std::cout << "nullptr" | ||||
|                   << "\n"; | ||||
|       } else { | ||||
|         factor->print(ss.str(), keyFormatter); | ||||
| 
 | ||||
|         if (hc->isContinuous()) { | ||||
|           factor->print(ss.str(), keyFormatter); | ||||
|           std::cout << "error = " << hc->asGaussian()->error(values) << "\n"; | ||||
|         } else if (hc->isDiscrete()) { | ||||
|           std::cout << "error = "; | ||||
|           hc->asDiscrete()->errorTree().print("", keyFormatter); | ||||
|           std::cout << "\n"; | ||||
|           factor->print(ss.str(), keyFormatter); | ||||
|           std::cout << "error = " << hc->asDiscrete()->error(values.discrete()) | ||||
|                     << "\n"; | ||||
|         } else { | ||||
|           // Is hybrid
 | ||||
|           std::cout << "error = "; | ||||
|           hc->asMixture()->errorTree(values.continuous()).print(); | ||||
|           std::cout << "\n"; | ||||
|           auto mixtureComponent = | ||||
|               hc->asMixture()->operator()(values.discrete()); | ||||
|           mixtureComponent->print(ss.str(), keyFormatter); | ||||
|           std::cout << "error = " << mixtureComponent->error(values) << "\n"; | ||||
|         } | ||||
|       } | ||||
|     } else if (auto gf = std::dynamic_pointer_cast<GaussianFactor>(factor)) { | ||||
|  | @ -140,8 +138,7 @@ void HybridGaussianFactorGraph::printErrors( | |||
|                   << "\n"; | ||||
|       } else { | ||||
|         factor->print(ss.str(), keyFormatter); | ||||
|         std::cout << "error = "; | ||||
|         df->errorTree().print("", keyFormatter); | ||||
|         std::cout << "error = " << df->error(values.discrete()) << std::endl; | ||||
|       } | ||||
| 
 | ||||
|     } else { | ||||
|  | @ -233,6 +230,25 @@ continuousElimination(const HybridGaussianFactorGraph &factors, | |||
|   return {std::make_shared<HybridConditional>(result.first), result.second}; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************ */ | ||||
| /**
 | ||||
|  * @brief Exponentiate log-values, not necessarily normalized, normalize, and | ||||
|  * return as AlgebraicDecisionTree<Key>. | ||||
|  * | ||||
|  * @param logValues DecisionTree of (unnormalized) log values. | ||||
|  * @return AlgebraicDecisionTree<Key> | ||||
|  */ | ||||
| static AlgebraicDecisionTree<Key> probabilitiesFromLogValues( | ||||
|     const AlgebraicDecisionTree<Key> &logValues) { | ||||
|   // Perform normalization
 | ||||
|   double max_log = logValues.max(); | ||||
|   AlgebraicDecisionTree<Key> probabilities = DecisionTree<Key, double>( | ||||
|       logValues, [&max_log](const double x) { return exp(x - max_log); }); | ||||
|   probabilities = probabilities.normalize(probabilities.sum()); | ||||
| 
 | ||||
|   return probabilities; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************ */ | ||||
| static std::pair<HybridConditional::shared_ptr, std::shared_ptr<Factor>> | ||||
| discreteElimination(const HybridGaussianFactorGraph &factors, | ||||
|  | @ -242,6 +258,22 @@ discreteElimination(const HybridGaussianFactorGraph &factors, | |||
|   for (auto &f : factors) { | ||||
|     if (auto df = dynamic_pointer_cast<DiscreteFactor>(f)) { | ||||
|       dfg.push_back(df); | ||||
|     } else if (auto gmf = dynamic_pointer_cast<GaussianMixtureFactor>(f)) { | ||||
|       // Case where we have a GaussianMixtureFactor with no continuous keys.
 | ||||
|       // In this case, compute discrete probabilities.
 | ||||
|       auto logProbability = | ||||
|           [&](const GaussianFactor::shared_ptr &factor) -> double { | ||||
|         if (!factor) return 0.0; | ||||
|         return -factor->error(VectorValues()); | ||||
|       }; | ||||
|       AlgebraicDecisionTree<Key> logProbabilities = | ||||
|           DecisionTree<Key, double>(gmf->factors(), logProbability); | ||||
| 
 | ||||
|       AlgebraicDecisionTree<Key> probabilities = | ||||
|           probabilitiesFromLogValues(logProbabilities); | ||||
|       dfg.emplace_shared<DecisionTreeFactor>(gmf->discreteKeys(), | ||||
|                                              probabilities); | ||||
| 
 | ||||
|     } else if (auto orphan = dynamic_pointer_cast<OrphanWrapper>(f)) { | ||||
|       // Ignore orphaned clique.
 | ||||
|       // TODO(dellaert): is this correct? If so explain here.
 | ||||
|  | @ -279,21 +311,32 @@ GaussianFactorGraphTree removeEmpty(const GaussianFactorGraphTree &sum) { | |||
| using Result = std::pair<std::shared_ptr<GaussianConditional>, | ||||
|                          GaussianMixtureFactor::sharedFactor>; | ||||
| 
 | ||||
| // Integrate the probability mass in the last continuous conditional using
 | ||||
| // the unnormalized probability q(μ;m) = exp(-error(μ;m)) at the mean.
 | ||||
| //   discrete_probability = exp(-error(μ;m)) * sqrt(det(2π Σ_m))
 | ||||
| /**
 | ||||
|  * Compute the probability p(μ;m) = exp(-error(μ;m)) * sqrt(det(2π Σ_m) | ||||
|  * from the residual error ||b||^2 at the mean μ. | ||||
|  * The residual error contains no keys, and only | ||||
|  * depends on the discrete separator if present. | ||||
|  */ | ||||
| static std::shared_ptr<Factor> createDiscreteFactor( | ||||
|     const DecisionTree<Key, Result> &eliminationResults, | ||||
|     const DiscreteKeys &discreteSeparator) { | ||||
|   auto probability = [&](const Result &pair) -> double { | ||||
|   auto logProbability = [&](const Result &pair) -> double { | ||||
|     const auto &[conditional, factor] = pair; | ||||
|     static const VectorValues kEmpty; | ||||
|     // If the factor is not null, it has no keys, just contains the residual.
 | ||||
|     if (!factor) return 1.0;  // TODO(dellaert): not loving this.
 | ||||
|     return exp(-factor->error(kEmpty)) / conditional->normalizationConstant(); | ||||
| 
 | ||||
|     // Logspace version of:
 | ||||
|     // exp(-factor->error(kEmpty)) / conditional->normalizationConstant();
 | ||||
|     // We take negative of the logNormalizationConstant `log(1/k)`
 | ||||
|     // to get `log(k)`.
 | ||||
|     return -factor->error(kEmpty) - conditional->logNormalizationConstant(); | ||||
|   }; | ||||
| 
 | ||||
|   DecisionTree<Key, double> probabilities(eliminationResults, probability); | ||||
|   AlgebraicDecisionTree<Key> logProbabilities( | ||||
|       DecisionTree<Key, double>(eliminationResults, logProbability)); | ||||
|   AlgebraicDecisionTree<Key> probabilities = | ||||
|       probabilitiesFromLogValues(logProbabilities); | ||||
| 
 | ||||
|   return std::make_shared<DecisionTreeFactor>(discreteSeparator, probabilities); | ||||
| } | ||||
|  | @ -480,18 +523,9 @@ EliminateHybrid(const HybridGaussianFactorGraph &factors, | |||
|         std::inserter(continuousSeparator, continuousSeparator.begin())); | ||||
| 
 | ||||
|     // Similarly for the discrete separator.
 | ||||
|     KeySet discreteSeparatorSet; | ||||
|     std::set<DiscreteKey> discreteSeparator; | ||||
|     auto discreteKeySet = factors.discreteKeySet(); | ||||
|     std::set_difference( | ||||
|         discreteKeySet.begin(), discreteKeySet.end(), frontalKeysSet.begin(), | ||||
|         frontalKeysSet.end(), | ||||
|         std::inserter(discreteSeparatorSet, discreteSeparatorSet.begin())); | ||||
|     // Convert from set of keys to set of DiscreteKeys
 | ||||
|     auto discreteKeyMap = factors.discreteKeyMap(); | ||||
|     for (auto key : discreteSeparatorSet) { | ||||
|       discreteSeparator.insert(discreteKeyMap.at(key)); | ||||
|     } | ||||
|     // Since we eliminate all continuous variables first,
 | ||||
|     // the discrete separator will be *all* the discrete keys.
 | ||||
|     std::set<DiscreteKey> discreteSeparator = factors.discreteKeys(); | ||||
| 
 | ||||
|     return hybridElimination(factors, frontalKeys, continuousSeparator, | ||||
|                              discreteSeparator); | ||||
|  | @ -504,10 +538,15 @@ AlgebraicDecisionTree<Key> HybridGaussianFactorGraph::errorTree( | |||
|   AlgebraicDecisionTree<Key> error_tree(0.0); | ||||
| 
 | ||||
|   // Iterate over each factor.
 | ||||
|   for (auto &f : factors_) { | ||||
|   for (auto &factor : factors_) { | ||||
|     // TODO(dellaert): just use a virtual method defined in HybridFactor.
 | ||||
|     AlgebraicDecisionTree<Key> factor_error; | ||||
| 
 | ||||
|     auto f = factor; | ||||
|     if (auto hc = dynamic_pointer_cast<HybridConditional>(factor)) { | ||||
|       f = hc->inner(); | ||||
|     } | ||||
| 
 | ||||
|     if (auto gaussianMixture = dynamic_pointer_cast<GaussianMixtureFactor>(f)) { | ||||
|       // Compute factor error and add it.
 | ||||
|       error_tree = error_tree + gaussianMixture->errorTree(continuousValues); | ||||
|  |  | |||
|  | @ -144,6 +144,14 @@ class GTSAM_EXPORT HybridGaussianFactorGraph | |||
|   //     const std::string& s = "HybridGaussianFactorGraph",
 | ||||
|   //     const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;
 | ||||
| 
 | ||||
|   /**
 | ||||
|    * @brief Print the errors of each factor in the hybrid factor graph. | ||||
|    * | ||||
|    * @param values The HybridValues for the variables used to compute the error. | ||||
|    * @param str String that is output before the factor graph and errors. | ||||
|    * @param keyFormatter Formatter function for the keys in the factors. | ||||
|    * @param printCondition A condition to check if a factor should be printed. | ||||
|    */ | ||||
|   void printErrors( | ||||
|       const HybridValues& values, | ||||
|       const std::string& str = "HybridGaussianFactorGraph: ", | ||||
|  |  | |||
|  | @ -22,9 +22,13 @@ | |||
| #include <gtsam/discrete/DiscreteValues.h> | ||||
| #include <gtsam/hybrid/GaussianMixture.h> | ||||
| #include <gtsam/hybrid/GaussianMixtureFactor.h> | ||||
| #include <gtsam/hybrid/HybridBayesNet.h> | ||||
| #include <gtsam/hybrid/HybridGaussianFactorGraph.h> | ||||
| #include <gtsam/hybrid/HybridValues.h> | ||||
| #include <gtsam/inference/Symbol.h> | ||||
| #include <gtsam/linear/GaussianFactorGraph.h> | ||||
| #include <gtsam/nonlinear/PriorFactor.h> | ||||
| #include <gtsam/slam/BetweenFactor.h> | ||||
| 
 | ||||
| // Include for test suite
 | ||||
| #include <CppUnitLite/TestHarness.h> | ||||
|  | @ -32,8 +36,10 @@ | |||
| using namespace std; | ||||
| using namespace gtsam; | ||||
| using noiseModel::Isotropic; | ||||
| using symbol_shorthand::F; | ||||
| using symbol_shorthand::M; | ||||
| using symbol_shorthand::X; | ||||
| using symbol_shorthand::Z; | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| // Check iterators of empty mixture.
 | ||||
|  | @ -56,7 +62,6 @@ TEST(GaussianMixtureFactor, Sum) { | |||
|   auto b = Matrix::Zero(2, 1); | ||||
|   Vector2 sigmas; | ||||
|   sigmas << 1, 2; | ||||
|   auto model = noiseModel::Diagonal::Sigmas(sigmas, true); | ||||
| 
 | ||||
|   auto f10 = std::make_shared<JacobianFactor>(X(1), A1, X(2), A2, b); | ||||
|   auto f11 = std::make_shared<JacobianFactor>(X(1), A1, X(2), A2, b); | ||||
|  | @ -106,7 +111,8 @@ TEST(GaussianMixtureFactor, Printing) { | |||
|   GaussianMixtureFactor mixtureFactor({X(1), X(2)}, {m1}, factors); | ||||
| 
 | ||||
|   std::string expected = | ||||
|       R"(Hybrid [x1 x2; 1]{ | ||||
|       R"(GaussianMixtureFactor | ||||
| Hybrid [x1 x2; 1]{ | ||||
|  Choice(1)  | ||||
|  0 Leaf : | ||||
|   A[x1] = [ | ||||
|  | @ -178,7 +184,8 @@ TEST(GaussianMixtureFactor, Error) { | |||
|   continuousValues.insert(X(2), Vector2(1, 1)); | ||||
| 
 | ||||
|   // error should return a tree of errors, with nodes for each discrete value.
 | ||||
|   AlgebraicDecisionTree<Key> error_tree = mixtureFactor.errorTree(continuousValues); | ||||
|   AlgebraicDecisionTree<Key> error_tree = | ||||
|       mixtureFactor.errorTree(continuousValues); | ||||
| 
 | ||||
|   std::vector<DiscreteKey> discrete_keys = {m1}; | ||||
|   // Error values for regression test
 | ||||
|  | @ -191,8 +198,390 @@ TEST(GaussianMixtureFactor, Error) { | |||
|   DiscreteValues discreteValues; | ||||
|   discreteValues[m1.first] = 1; | ||||
|   EXPECT_DOUBLES_EQUAL( | ||||
|       4.0, mixtureFactor.error({continuousValues, discreteValues}), | ||||
|       1e-9); | ||||
|       4.0, mixtureFactor.error({continuousValues, discreteValues}), 1e-9); | ||||
| } | ||||
| 
 | ||||
| namespace test_gmm { | ||||
| 
 | ||||
| /**
 | ||||
|  * Function to compute P(m=1|z). For P(m=0|z), swap mus and sigmas. | ||||
|  * If sigma0 == sigma1, it simplifies to a sigmoid function. | ||||
|  * | ||||
|  * Follows equation 7.108 since it is more generic. | ||||
|  */ | ||||
| double prob_m_z(double mu0, double mu1, double sigma0, double sigma1, | ||||
|                 double z) { | ||||
|   double x1 = ((z - mu0) / sigma0), x2 = ((z - mu1) / sigma1); | ||||
|   double d = sigma1 / sigma0; | ||||
|   double e = d * std::exp(-0.5 * (x1 * x1 - x2 * x2)); | ||||
|   return 1 / (1 + e); | ||||
| }; | ||||
| 
 | ||||
| static HybridBayesNet GetGaussianMixtureModel(double mu0, double mu1, | ||||
|                                               double sigma0, double sigma1) { | ||||
|   DiscreteKey m(M(0), 2); | ||||
|   Key z = Z(0); | ||||
| 
 | ||||
|   auto model0 = noiseModel::Isotropic::Sigma(1, sigma0); | ||||
|   auto model1 = noiseModel::Isotropic::Sigma(1, sigma1); | ||||
| 
 | ||||
|   auto c0 = make_shared<GaussianConditional>(z, Vector1(mu0), I_1x1, model0), | ||||
|        c1 = make_shared<GaussianConditional>(z, Vector1(mu1), I_1x1, model1); | ||||
| 
 | ||||
|   auto gm = new GaussianMixture({z}, {}, {m}, {c0, c1}); | ||||
|   auto mixing = new DiscreteConditional(m, "0.5/0.5"); | ||||
| 
 | ||||
|   HybridBayesNet hbn; | ||||
|   hbn.emplace_back(gm); | ||||
|   hbn.emplace_back(mixing); | ||||
| 
 | ||||
|   return hbn; | ||||
| } | ||||
| 
 | ||||
| }  // namespace test_gmm
 | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Test a simple Gaussian Mixture Model represented as P(m)P(z|m) | ||||
|  * where m is a discrete variable and z is a continuous variable. | ||||
|  * m is binary and depending on m, we have 2 different means | ||||
|  * μ1 and μ2 for the Gaussian distribution around which we sample z. | ||||
|  * | ||||
|  * The resulting factor graph should eliminate to a Bayes net | ||||
|  * which represents a sigmoid function. | ||||
|  */ | ||||
| TEST(GaussianMixtureFactor, GaussianMixtureModel) { | ||||
|   using namespace test_gmm; | ||||
| 
 | ||||
|   double mu0 = 1.0, mu1 = 3.0; | ||||
|   double sigma = 2.0; | ||||
| 
 | ||||
|   DiscreteKey m(M(0), 2); | ||||
|   Key z = Z(0); | ||||
| 
 | ||||
|   auto hbn = GetGaussianMixtureModel(mu0, mu1, sigma, sigma); | ||||
| 
 | ||||
|   // The result should be a sigmoid.
 | ||||
|   // So should be P(m=1|z) = 0.5 at z=3.0 - 1.0=2.0
 | ||||
|   double midway = mu1 - mu0, lambda = 4; | ||||
|   { | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(midway)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma, sigma, midway), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{m.first, 1}}), | ||||
|         1e-8); | ||||
| 
 | ||||
|     // At the halfway point between the means, we should get P(m|z)=0.5
 | ||||
|     HybridBayesNet expected; | ||||
|     expected.emplace_back(new DiscreteConditional(m, "0.5/0.5")); | ||||
| 
 | ||||
|     EXPECT(assert_equal(expected, *bn)); | ||||
|   } | ||||
|   { | ||||
|     // Shift by -lambda
 | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(midway - lambda)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma, sigma, midway - lambda), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{m.first, 1}}), | ||||
|         1e-8); | ||||
|   } | ||||
|   { | ||||
|     // Shift by lambda
 | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(midway + lambda)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma, sigma, midway + lambda), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{m.first, 1}}), | ||||
|         1e-8); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Test a simple Gaussian Mixture Model represented as P(m)P(z|m) | ||||
|  * where m is a discrete variable and z is a continuous variable. | ||||
|  * m is binary and depending on m, we have 2 different means | ||||
|  * and covariances each for the | ||||
|  * Gaussian distribution around which we sample z. | ||||
|  * | ||||
|  * The resulting factor graph should eliminate to a Bayes net | ||||
|  * which represents a Gaussian-like function | ||||
|  * where m1>m0 close to 3.1333. | ||||
|  */ | ||||
| TEST(GaussianMixtureFactor, GaussianMixtureModel2) { | ||||
|   using namespace test_gmm; | ||||
| 
 | ||||
|   double mu0 = 1.0, mu1 = 3.0; | ||||
|   double sigma0 = 8.0, sigma1 = 4.0; | ||||
| 
 | ||||
|   DiscreteKey m(M(0), 2); | ||||
|   Key z = Z(0); | ||||
| 
 | ||||
|   auto hbn = GetGaussianMixtureModel(mu0, mu1, sigma0, sigma1); | ||||
| 
 | ||||
|   double m1_high = 3.133, lambda = 4; | ||||
|   { | ||||
|     // The result should be a bell curve like function
 | ||||
|     // with m1 > m0 close to 3.1333.
 | ||||
|     // We get 3.1333 by finding the maximum value of the function.
 | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(3.133)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma0, sigma1, m1_high), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{M(0), 1}}), 1e-8); | ||||
| 
 | ||||
|     // At the halfway point between the means
 | ||||
|     HybridBayesNet expected; | ||||
|     expected.emplace_back(new DiscreteConditional( | ||||
|         m, {}, | ||||
|         vector<double>{prob_m_z(mu1, mu0, sigma1, sigma0, m1_high), | ||||
|                        prob_m_z(mu0, mu1, sigma0, sigma1, m1_high)})); | ||||
| 
 | ||||
|     EXPECT(assert_equal(expected, *bn)); | ||||
|   } | ||||
|   { | ||||
|     // Shift by -lambda
 | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(m1_high - lambda)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma0, sigma1, m1_high - lambda), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{m.first, 1}}), | ||||
|         1e-8); | ||||
|   } | ||||
|   { | ||||
|     // Shift by lambda
 | ||||
|     VectorValues given; | ||||
|     given.insert(z, Vector1(m1_high + lambda)); | ||||
| 
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     EXPECT_DOUBLES_EQUAL( | ||||
|         prob_m_z(mu0, mu1, sigma0, sigma1, m1_high + lambda), | ||||
|         bn->at(0)->asDiscrete()->operator()(DiscreteValues{{m.first, 1}}), | ||||
|         1e-8); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| namespace test_two_state_estimation { | ||||
| 
 | ||||
| /// Create Two State Bayes Network with measurements
 | ||||
| static HybridBayesNet CreateBayesNet(double mu0, double mu1, double sigma0, | ||||
|                                      double sigma1, | ||||
|                                      bool add_second_measurement = false, | ||||
|                                      double prior_sigma = 1e-3, | ||||
|                                      double measurement_sigma = 3.0) { | ||||
|   DiscreteKey m1(M(1), 2); | ||||
|   Key z0 = Z(0), z1 = Z(1); | ||||
|   Key x0 = X(0), x1 = X(1); | ||||
| 
 | ||||
|   HybridBayesNet hbn; | ||||
| 
 | ||||
|   auto measurement_model = noiseModel::Isotropic::Sigma(1, measurement_sigma); | ||||
|   // Add measurement P(z0 | x0)
 | ||||
|   auto p_z0 = new GaussianConditional(z0, Vector1(0.0), -I_1x1, x0, I_1x1, | ||||
|                                       measurement_model); | ||||
|   hbn.emplace_back(p_z0); | ||||
| 
 | ||||
|   // Add hybrid motion model
 | ||||
|   auto model0 = noiseModel::Isotropic::Sigma(1, sigma0); | ||||
|   auto model1 = noiseModel::Isotropic::Sigma(1, sigma1); | ||||
|   auto c0 = make_shared<GaussianConditional>(x1, Vector1(mu0), I_1x1, x0, | ||||
|                                              -I_1x1, model0), | ||||
|        c1 = make_shared<GaussianConditional>(x1, Vector1(mu1), I_1x1, x0, | ||||
|                                              -I_1x1, model1); | ||||
| 
 | ||||
|   auto motion = new GaussianMixture({x1}, {x0}, {m1}, {c0, c1}); | ||||
|   hbn.emplace_back(motion); | ||||
| 
 | ||||
|   if (add_second_measurement) { | ||||
|     // Add second measurement
 | ||||
|     auto p_z1 = new GaussianConditional(z1, Vector1(0.0), -I_1x1, x1, I_1x1, | ||||
|                                         measurement_model); | ||||
|     hbn.emplace_back(p_z1); | ||||
|   } | ||||
| 
 | ||||
|   // Discrete uniform prior.
 | ||||
|   auto p_m1 = new DiscreteConditional(m1, "0.5/0.5"); | ||||
|   hbn.emplace_back(p_m1); | ||||
| 
 | ||||
|   return hbn; | ||||
| } | ||||
| 
 | ||||
| }  // namespace test_two_state_estimation
 | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Test a model P(z0|x0)P(x1|x0,m1)P(z1|x1)P(m1). | ||||
|  * | ||||
|  * P(f01|x1,x0,m1) has different means and same covariance. | ||||
|  * | ||||
|  * Converting to a factor graph gives us | ||||
|  * ϕ(x0)ϕ(x1,x0,m1)ϕ(x1)P(m1) | ||||
|  * | ||||
|  * If we only have a measurement on z0, then | ||||
|  * the probability of m1 should be 0.5/0.5. | ||||
|  * Getting a measurement on z1 gives use more information. | ||||
|  */ | ||||
| TEST(GaussianMixtureFactor, TwoStateModel) { | ||||
|   using namespace test_two_state_estimation; | ||||
| 
 | ||||
|   double mu0 = 1.0, mu1 = 3.0; | ||||
|   double sigma = 2.0; | ||||
| 
 | ||||
|   DiscreteKey m1(M(1), 2); | ||||
|   Key z0 = Z(0), z1 = Z(1); | ||||
| 
 | ||||
|   // Start with no measurement on x1, only on x0
 | ||||
|   HybridBayesNet hbn = CreateBayesNet(mu0, mu1, sigma, sigma, false); | ||||
| 
 | ||||
|   VectorValues given; | ||||
|   given.insert(z0, Vector1(0.5)); | ||||
| 
 | ||||
|   { | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     // Since no measurement on x1, we hedge our bets
 | ||||
|     DiscreteConditional expected(m1, "0.5/0.5"); | ||||
|     EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()))); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     // Now we add a measurement z1 on x1
 | ||||
|     hbn = CreateBayesNet(mu0, mu1, sigma, sigma, true); | ||||
| 
 | ||||
|     // If we see z1=2.6 (> 2.5 which is the halfway point),
 | ||||
|     // discrete mode should say m1=1
 | ||||
|     given.insert(z1, Vector1(2.6)); | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     // Since we have a measurement on z2, we get a definite result
 | ||||
|     DiscreteConditional expected(m1, "0.49772729/0.50227271"); | ||||
|     // regression
 | ||||
|     EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 1e-6)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Test a model P(z0|x0)P(x1|x0,m1)P(z1|x1)P(m1). | ||||
|  * | ||||
|  * P(f01|x1,x0,m1) has different means and different covariances. | ||||
|  * | ||||
|  * Converting to a factor graph gives us | ||||
|  * ϕ(x0)ϕ(x1,x0,m1)ϕ(x1)P(m1) | ||||
|  * | ||||
|  * If we only have a measurement on z0, then | ||||
|  * the P(m1) should be 0.5/0.5. | ||||
|  * Getting a measurement on z1 gives use more information. | ||||
|  */ | ||||
| TEST(GaussianMixtureFactor, TwoStateModel2) { | ||||
|   using namespace test_two_state_estimation; | ||||
| 
 | ||||
|   double mu0 = 1.0, mu1 = 3.0; | ||||
|   double sigma0 = 6.0, sigma1 = 4.0; | ||||
|   auto model0 = noiseModel::Isotropic::Sigma(1, sigma0); | ||||
|   auto model1 = noiseModel::Isotropic::Sigma(1, sigma1); | ||||
| 
 | ||||
|   DiscreteKey m1(M(1), 2); | ||||
|   Key z0 = Z(0), z1 = Z(1); | ||||
| 
 | ||||
|   // Start with no measurement on x1, only on x0
 | ||||
|   HybridBayesNet hbn = CreateBayesNet(mu0, mu1, sigma0, sigma1, false); | ||||
| 
 | ||||
|   VectorValues given; | ||||
|   given.insert(z0, Vector1(0.5)); | ||||
| 
 | ||||
|   { | ||||
|     // Start with no measurement on x1, only on x0
 | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
| 
 | ||||
|     { | ||||
|       VectorValues vv{ | ||||
|           {X(0), Vector1(0.0)}, {X(1), Vector1(1.0)}, {Z(0), Vector1(0.5)}}; | ||||
|       HybridValues hv0(vv, DiscreteValues{{M(1), 0}}), | ||||
|           hv1(vv, DiscreteValues{{M(1), 1}}); | ||||
|       EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0), | ||||
|                            gfg.error(hv1) / hbn.error(hv1), 1e-9); | ||||
|     } | ||||
|     { | ||||
|       VectorValues vv{ | ||||
|           {X(0), Vector1(0.5)}, {X(1), Vector1(3.0)}, {Z(0), Vector1(0.5)}}; | ||||
|       HybridValues hv0(vv, DiscreteValues{{M(1), 0}}), | ||||
|           hv1(vv, DiscreteValues{{M(1), 1}}); | ||||
|       EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0), | ||||
|                            gfg.error(hv1) / hbn.error(hv1), 1e-9); | ||||
|     } | ||||
| 
 | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     // Since no measurement on x1, we a 50/50 probability
 | ||||
|     auto p_m = bn->at(2)->asDiscrete(); | ||||
|     EXPECT_DOUBLES_EQUAL(0.5, p_m->operator()(DiscreteValues{{m1.first, 0}}), | ||||
|                          1e-9); | ||||
|     EXPECT_DOUBLES_EQUAL(0.5, p_m->operator()(DiscreteValues{{m1.first, 1}}), | ||||
|                          1e-9); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     // Now we add a measurement z1 on x1
 | ||||
|     hbn = CreateBayesNet(mu0, mu1, sigma0, sigma1, true); | ||||
| 
 | ||||
|     given.insert(z1, Vector1(2.2)); | ||||
|     HybridGaussianFactorGraph gfg = hbn.toFactorGraph(given); | ||||
| 
 | ||||
|     { | ||||
|       VectorValues vv{{X(0), Vector1(0.0)}, | ||||
|                       {X(1), Vector1(1.0)}, | ||||
|                       {Z(0), Vector1(0.5)}, | ||||
|                       {Z(1), Vector1(2.2)}}; | ||||
|       HybridValues hv0(vv, DiscreteValues{{M(1), 0}}), | ||||
|           hv1(vv, DiscreteValues{{M(1), 1}}); | ||||
|       EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0), | ||||
|                            gfg.error(hv1) / hbn.error(hv1), 1e-9); | ||||
|     } | ||||
|     { | ||||
|       VectorValues vv{{X(0), Vector1(0.5)}, | ||||
|                       {X(1), Vector1(3.0)}, | ||||
|                       {Z(0), Vector1(0.5)}, | ||||
|                       {Z(1), Vector1(2.2)}}; | ||||
|       HybridValues hv0(vv, DiscreteValues{{M(1), 0}}), | ||||
|           hv1(vv, DiscreteValues{{M(1), 1}}); | ||||
|       EXPECT_DOUBLES_EQUAL(gfg.error(hv0) / hbn.error(hv0), | ||||
|                            gfg.error(hv1) / hbn.error(hv1), 1e-9); | ||||
|     } | ||||
| 
 | ||||
|     HybridBayesNet::shared_ptr bn = gfg.eliminateSequential(); | ||||
| 
 | ||||
|     // Since we have a measurement on z2, we get a definite result
 | ||||
|     DiscreteConditional expected(m1, "0.44744586/0.55255414"); | ||||
|     // regression
 | ||||
|     EXPECT(assert_equal(expected, *(bn->at(2)->asDiscrete()), 1e-6)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
|  | @ -200,4 +589,4 @@ int main() { | |||
|   TestResult tr; | ||||
|   return TestRegistry::runAllTests(tr); | ||||
| } | ||||
| /* ************************************************************************* */ | ||||
| /* ************************************************************************* */ | ||||
|  |  | |||
|  | @ -598,6 +598,57 @@ TEST(HybridGaussianFactorGraph, ErrorAndProbPrimeTree) { | |||
|   EXPECT(assert_equal(expected_probs, probs, 1e-7)); | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************/ | ||||
| // Test hybrid gaussian factor graph errorTree when there is a HybridConditional in the graph
 | ||||
| TEST(HybridGaussianFactorGraph, ErrorTreeWithConditional) { | ||||
|   using symbol_shorthand::F; | ||||
| 
 | ||||
|   DiscreteKey m1(M(1), 2); | ||||
|   Key z0 = Z(0), f01 = F(0); | ||||
|   Key x0 = X(0), x1 = X(1); | ||||
| 
 | ||||
|   HybridBayesNet hbn; | ||||
| 
 | ||||
|   auto prior_model = noiseModel::Isotropic::Sigma(1, 1e-1); | ||||
|   auto measurement_model = noiseModel::Isotropic::Sigma(1, 2.0); | ||||
| 
 | ||||
|   // Set a prior P(x0) at x0=0
 | ||||
|   hbn.emplace_back( | ||||
|       new GaussianConditional(x0, Vector1(0.0), I_1x1, prior_model)); | ||||
| 
 | ||||
|   // Add measurement P(z0 | x0)
 | ||||
|   hbn.emplace_back(new GaussianConditional(z0, Vector1(0.0), -I_1x1, x0, I_1x1, | ||||
|                                            measurement_model)); | ||||
| 
 | ||||
|   // Add hybrid motion model
 | ||||
|   double mu = 0.0; | ||||
|   double sigma0 = 1e2, sigma1 = 1e-2; | ||||
|   auto model0 = noiseModel::Isotropic::Sigma(1, sigma0); | ||||
|   auto model1 = noiseModel::Isotropic::Sigma(1, sigma1); | ||||
|   auto c0 = make_shared<GaussianConditional>(f01, Vector1(mu), I_1x1, x1, I_1x1, | ||||
|                                              x0, -I_1x1, model0), | ||||
|        c1 = make_shared<GaussianConditional>(f01, Vector1(mu), I_1x1, x1, I_1x1, | ||||
|                                              x0, -I_1x1, model1); | ||||
|   hbn.emplace_back(new GaussianMixture({f01}, {x0, x1}, {m1}, {c0, c1})); | ||||
| 
 | ||||
|   // Discrete uniform prior.
 | ||||
|   hbn.emplace_back(new DiscreteConditional(m1, "0.5/0.5")); | ||||
| 
 | ||||
|   VectorValues given; | ||||
|   given.insert(z0, Vector1(0.0)); | ||||
|   given.insert(f01, Vector1(0.0)); | ||||
|   auto gfg = hbn.toFactorGraph(given); | ||||
| 
 | ||||
|   VectorValues vv; | ||||
|   vv.insert(x0, Vector1(1.0)); | ||||
|   vv.insert(x1, Vector1(2.0)); | ||||
|   AlgebraicDecisionTree<Key> errorTree = gfg.errorTree(vv); | ||||
| 
 | ||||
|   // regression
 | ||||
|   AlgebraicDecisionTree<Key> expected(m1, 59.335390372, 5050.125); | ||||
|   EXPECT(assert_equal(expected, errorTree, 1e-9)); | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************/ | ||||
| // Check that assembleGraphTree assembles Gaussian factor graphs for each
 | ||||
| // assignment.
 | ||||
|  |  | |||
|  | @ -510,6 +510,7 @@ factor 0: | |||
|   b = [ -10 ] | ||||
|   No noise model | ||||
| factor 1:  | ||||
| GaussianMixtureFactor | ||||
| Hybrid [x0 x1; m0]{ | ||||
|  Choice(m0)  | ||||
|  0 Leaf : | ||||
|  | @ -534,6 +535,7 @@ Hybrid [x0 x1; m0]{ | |||
| 
 | ||||
| } | ||||
| factor 2:  | ||||
| GaussianMixtureFactor | ||||
| Hybrid [x1 x2; m1]{ | ||||
|  Choice(m1)  | ||||
|  0 Leaf : | ||||
|  | @ -675,6 +677,8 @@ factor 6:  P( m1 | m0 ): | |||
| size: 3 | ||||
| conditional 0: Hybrid  P( x0 | x1 m0) | ||||
|  Discrete Keys = (m0, 2),  | ||||
|  logNormalizationConstant: 1.38862 | ||||
| 
 | ||||
|  Choice(m0)  | ||||
|  0 Leaf p(x0 | x1) | ||||
|   R = [ 10.0499 ] | ||||
|  | @ -692,6 +696,8 @@ conditional 0: Hybrid  P( x0 | x1 m0) | |||
| 
 | ||||
| conditional 1: Hybrid  P( x1 | x2 m0 m1) | ||||
|  Discrete Keys = (m0, 2), (m1, 2),  | ||||
|  logNormalizationConstant: 1.3935 | ||||
| 
 | ||||
|  Choice(m1)  | ||||
|  0 Choice(m0)  | ||||
|  0 0 Leaf p(x1 | x2) | ||||
|  | @ -725,6 +731,8 @@ conditional 1: Hybrid  P( x1 | x2 m0 m1) | |||
| 
 | ||||
| conditional 2: Hybrid  P( x2 | m0 m1) | ||||
|  Discrete Keys = (m0, 2), (m1, 2),  | ||||
|  logNormalizationConstant: 1.38857 | ||||
| 
 | ||||
|  Choice(m1)  | ||||
|  0 Choice(m0)  | ||||
|  0 0 Leaf p(x2) | ||||
|  |  | |||
|  | @ -18,6 +18,9 @@ | |||
| 
 | ||||
| #include <gtsam/base/TestableAssertions.h> | ||||
| #include <gtsam/discrete/DiscreteValues.h> | ||||
| #include <gtsam/hybrid/HybridBayesNet.h> | ||||
| #include <gtsam/hybrid/HybridGaussianFactorGraph.h> | ||||
| #include <gtsam/hybrid/HybridNonlinearFactorGraph.h> | ||||
| #include <gtsam/hybrid/MixtureFactor.h> | ||||
| #include <gtsam/inference/Symbol.h> | ||||
| #include <gtsam/slam/BetweenFactor.h> | ||||
|  |  | |||
|  | @ -263,11 +263,6 @@ namespace gtsam { | |||
|     /** equals required by Testable for unit testing */ | ||||
|     bool equals(const VectorValues& x, double tol = 1e-9) const; | ||||
| 
 | ||||
|     /// Check equality.
 | ||||
|     friend bool operator==(const VectorValues& lhs, const VectorValues& rhs) { | ||||
|       return lhs.equals(rhs); | ||||
|     } | ||||
| 
 | ||||
|     /// @{
 | ||||
|     /// @name Advanced Interface
 | ||||
|     /// @{
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue