/* ---------------------------------------------------------------------------- * 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 GaussianFactorGraph.cpp * @brief Linear Factor Graph where all factors are Gaussians * @author Kai Ni * @author Christian Potthast * @author Richard Roberts * @author Frank Dellaert */ #include #include #include #include #include using namespace std; using namespace gtsam; namespace gtsam { // Explicitly instantiate so we don't have to include everywhere INSTANTIATE_FACTOR_GRAPH(GaussianFactor); /* ************************************************************************* */ GaussianFactorGraph::GaussianFactorGraph(const GaussianBayesNet& CBN) : FactorGraph (CBN) { } /* ************************************************************************* */ GaussianFactorGraph::Keys GaussianFactorGraph::keys() const { FastSet keys; BOOST_FOREACH(const sharedFactor& factor, *this) if (factor) keys.insert(factor->begin(), factor->end()); return keys; } /* ************************************************************************* */ void GaussianFactorGraph::permuteWithInverse( const Permutation& inversePermutation) { BOOST_FOREACH(const sharedFactor& factor, factors_) { factor->permuteWithInverse(inversePermutation); } } /* ************************************************************************* */ void GaussianFactorGraph::combine(const GaussianFactorGraph &lfg) { for (const_iterator factor = lfg.factors_.begin(); factor != lfg.factors_.end(); factor++) { push_back(*factor); } } /* ************************************************************************* */ GaussianFactorGraph GaussianFactorGraph::combine2( const GaussianFactorGraph& lfg1, const GaussianFactorGraph& lfg2) { // create new linear factor graph equal to the first one GaussianFactorGraph fg = lfg1; // add the second factors_ in the graph for (const_iterator factor = lfg2.factors_.begin(); factor != lfg2.factors_.end(); factor++) { fg.push_back(*factor); } return fg; } /* ************************************************************************* */ std::vector > GaussianFactorGraph::sparseJacobian( const std::vector& columnIndices) const { std::vector > entries; size_t i = 0; BOOST_FOREACH(const sharedFactor& factor, *this) { // Convert to JacobianFactor if necessary JacobianFactor::shared_ptr jacobianFactor( boost::dynamic_pointer_cast(factor)); if (!jacobianFactor) { HessianFactor::shared_ptr hessian(boost::dynamic_pointer_cast< HessianFactor>(factor)); if (hessian) jacobianFactor.reset(new JacobianFactor(*hessian)); else throw invalid_argument( "GaussianFactorGraph contains a factor that is neither a JacobianFactor nor a HessianFactor."); } // Add entries, adjusting the row index i std::vector > factorEntries( jacobianFactor->sparse(columnIndices)); entries.reserve(entries.size() + factorEntries.size()); for (size_t entry = 0; entry < factorEntries.size(); ++entry) entries.push_back(boost::make_tuple( factorEntries[entry].get<0> () + i, factorEntries[entry].get< 1> (), factorEntries[entry].get<2> ())); // Increment row index i += jacobianFactor->rows(); } return entries; } /* ************************************************************************* */ Matrix GaussianFactorGraph::denseJacobian() const { // combine all factors JacobianFactor combined(*CombineJacobians(*convertCastFactors > (), VariableSlots(*this))); return combined.matrix_augmented(); } /* ************************************************************************* */ // Helper functions for Combine static boost::tuple, size_t, size_t> countDims(const std::vector& factors, const VariableSlots& variableSlots) { #ifndef NDEBUG vector varDims(variableSlots.size(), numeric_limits::max()); #else vector varDims(variableSlots.size()); #endif size_t m = 0; size_t n = 0; { Index jointVarpos = 0; BOOST_FOREACH(const VariableSlots::value_type& slots, variableSlots) { assert(slots.second.size() == factors.size()); Index sourceFactorI = 0; BOOST_FOREACH(const Index sourceVarpos, slots.second) { if(sourceVarpos < numeric_limits::max()) { const JacobianFactor& sourceFactor = *factors[sourceFactorI]; size_t vardim = sourceFactor.getDim(sourceFactor.begin() + sourceVarpos); #ifndef NDEBUG if(varDims[jointVarpos] == numeric_limits::max()) { varDims[jointVarpos] = vardim; n += vardim; } else assert(varDims[jointVarpos] == vardim); #else varDims[jointVarpos] = vardim; n += vardim; break; #endif } ++ sourceFactorI; } ++ jointVarpos; } BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, factors) { m += factor->rows(); } } return boost::make_tuple(varDims, m, n); } /* ************************************************************************* */ JacobianFactor::shared_ptr CombineJacobians( const FactorGraph& factors, const VariableSlots& variableSlots) { const bool debug = ISDEBUG("CombineJacobians"); if (debug) factors.print("Combining factors: "); if (debug) variableSlots.print(); if (debug) cout << "Determine dimensions" << endl; tic(1, "countDims"); vector varDims; size_t m, n; boost::tie(varDims, m, n) = countDims(factors, variableSlots); if (debug) { cout << "Dims: " << m << " x " << n << "\n"; BOOST_FOREACH(const size_t dim, varDims) cout << dim << " "; cout << endl; } toc(1, "countDims"); if (debug) cout << "Sort rows" << endl; tic(2, "sort rows"); vector rowSources; rowSources.reserve(m); bool anyConstrained = false; for (size_t sourceFactorI = 0; sourceFactorI < factors.size(); ++sourceFactorI) { const JacobianFactor& sourceFactor(*factors[sourceFactorI]); sourceFactor.collectInfo(sourceFactorI, rowSources); if (sourceFactor.isConstrained()) anyConstrained = true; } assert(rowSources.size() == m); std::sort(rowSources.begin(), rowSources.end()); toc(2, "sort rows"); if (debug) cout << "Allocate new factor" << endl; tic(3, "allocate"); JacobianFactor::shared_ptr combined(new JacobianFactor()); combined->allocate(variableSlots, varDims, m); Vector sigmas(m); toc(3, "allocate"); if (debug) cout << "Copy rows" << endl; tic(4, "copy rows"); Index combinedSlot = 0; BOOST_FOREACH(const VariableSlots::value_type& varslot, variableSlots) { for (size_t row = 0; row < m; ++row) { const JacobianFactor::_RowSource& info(rowSources[row]); const JacobianFactor& source(*factors[info.factorI]); size_t sourceRow = info.factorRowI; Index sourceSlot = varslot.second[info.factorI]; combined->copyRow(source, sourceRow, sourceSlot, row, combinedSlot); } ++combinedSlot; } toc(4, "copy rows"); if (debug) cout << "Copy rhs (b), sigma, and firstNonzeroBlocks" << endl; tic(5, "copy vectors"); for (size_t row = 0; row < m; ++row) { const JacobianFactor::_RowSource& info(rowSources[row]); const JacobianFactor& source(*factors[info.factorI]); const size_t sourceRow = info.factorRowI; combined->getb()(row) = source.getb()(sourceRow); sigmas(row) = source.get_model()->sigmas()(sourceRow); } combined->copyFNZ(m, variableSlots.size(),rowSources); toc(5, "copy vectors"); if (debug) cout << "Create noise model from sigmas" << endl; tic(6, "noise model"); combined->setModel( anyConstrained,sigmas); toc(6, "noise model"); if (debug) cout << "Assert Invariants" << endl; combined->assertInvariants(); return combined; } /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminateJacobians(const FactorGraph< JacobianFactor>& factors, size_t nrFrontals) { tic(1, "Combine"); JacobianFactor::shared_ptr jointFactor = CombineJacobians(factors, VariableSlots(factors)); toc(1, "Combine"); tic(2, "eliminate"); GaussianConditional::shared_ptr gbn = jointFactor->eliminate(nrFrontals); toc(2, "eliminate"); return make_pair(gbn, jointFactor); } /* ************************************************************************* */ static FastMap findScatterAndDims (const FactorGraph& factors) { const bool debug = ISDEBUG("findScatterAndDims"); // The "scatter" is a map from global variable indices to slot indices in the // union of involved variables. We also include the dimensionality of the // variable. Scatter scatter; // First do the set union. BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, factors) { for(GaussianFactor::const_iterator variable = factor->begin(); variable != factor->end(); ++variable) { scatter.insert(make_pair(*variable, SlotEntry(0, factor->getDim(variable)))); } } // Next fill in the slot indices (we can only get these after doing the set // union. size_t slot = 0; BOOST_FOREACH(Scatter::value_type& var_slot, scatter) { var_slot.second.slot = (slot ++); if(debug) cout << "scatter[" << var_slot.first << "] = (slot " << var_slot.second.slot << ", dim " << var_slot.second.dimension << ")" << endl; } return scatter; } /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminateCholesky(const FactorGraph< GaussianFactor>& factors, size_t nrFrontals) { const bool debug = ISDEBUG("EliminateCholesky"); // Find the scatter and variable dimensions tic(1, "find scatter"); Scatter scatter(findScatterAndDims(factors)); toc(1, "find scatter"); // Pull out keys and dimensions tic(2, "keys"); vector keys(scatter.size()); vector dimensions(scatter.size() + 1); BOOST_FOREACH(const Scatter::value_type& var_slot, scatter) { keys[var_slot.second.slot] = var_slot.first; dimensions[var_slot.second.slot] = var_slot.second.dimension; } // This is for the r.h.s. vector dimensions.back() = 1; toc(2, "keys"); // Form Ab' * Ab tic(3, "combine"); HessianFactor::shared_ptr // combinedFactor(new HessianFactor(factors, dimensions, scatter)); toc(3, "combine"); // Do Cholesky, note that after this, the lower triangle still contains // some untouched non-zeros that should be zero. We zero them while // extracting submatrices next. tic(4, "partial Cholesky"); combinedFactor->partialCholesky(nrFrontals); toc(4, "partial Cholesky"); // Extract conditionals and fill in details of the remaining factor tic(5, "split"); GaussianConditional::shared_ptr conditionals = combinedFactor->splitEliminatedFactor(nrFrontals, keys); if (debug) { conditionals->print("Extracted conditionals: "); combinedFactor->print("Eliminated factor (L piece): "); } toc(5, "split"); combinedFactor->assertInvariants(); return make_pair(conditionals, combinedFactor); } /* ************************************************************************* */ static FactorGraph convertToJacobians(const FactorGraph< GaussianFactor>& factors) { typedef JacobianFactor J; typedef HessianFactor H; const bool debug = ISDEBUG("convertToJacobians"); FactorGraph jacobians; jacobians.reserve(factors.size()); BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, factors) if (factor) { J::shared_ptr jacobian(boost::dynamic_pointer_cast(factor)); if (jacobian) { jacobians.push_back(jacobian); if (debug) jacobian->print("Existing JacobianFactor: "); } else { H::shared_ptr hessian(boost::dynamic_pointer_cast(factor)); if (!hessian) throw std::invalid_argument( "convertToJacobians: factor is neither a JacobianFactor nor a HessianFactor."); J::shared_ptr converted(new J(*hessian)); if (debug) { if (!assert_equal(*hessian, HessianFactor(*converted), 1e-3)) throw runtime_error( "convertToJacobians: Conversion between Jacobian and Hessian incorrect"); cout << "Converted HessianFactor to JacobianFactor:\n"; hessian->print("HessianFactor: "); converted->print("JacobianFactor: "); } jacobians.push_back(converted); } } return jacobians; } /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminateQR(const FactorGraph< GaussianFactor>& factors, size_t nrFrontals) { const bool debug = ISDEBUG("EliminateQR"); // Convert all factors to the appropriate type and call the type-specific EliminateGaussian. if (debug) cout << "Using QR:"; tic(1, "convert to Jacobian"); FactorGraph jacobians = convertToJacobians(factors); toc(1, "convert to Jacobian"); tic(2, "Jacobian EliminateGaussian"); GaussianConditional::shared_ptr conditionals; GaussianFactor::shared_ptr factor; boost::tie(conditionals, factor) = EliminateJacobians(jacobians, nrFrontals); toc(2, "Jacobian EliminateGaussian"); return make_pair(conditionals, factor); } // \EliminateQR /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminatePreferCholesky( const FactorGraph& factors, size_t nrFrontals) { typedef JacobianFactor J; typedef HessianFactor H; // If any JacobianFactors have constrained noise models, we have to convert // all factors to JacobianFactors. Otherwise, we can convert all factors // to HessianFactors. This is because QR can handle constrained noise // models but Cholesky cannot. // Decide whether to use QR or Cholesky // Check if any JacobianFactors have constrained noise models. bool useQR = false; useQR = false; BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, factors) { J::shared_ptr jacobian(boost::dynamic_pointer_cast(factor)); if (jacobian && jacobian->get_model()->isConstrained()) { useQR = true; break; } } // Convert all factors to the appropriate type // and call the type-specific EliminateGaussian. if (useQR) return EliminateQR(factors, nrFrontals); GaussianFactorGraph::EliminationResult ret; try { tic(2, "EliminateCholesky"); ret = EliminateCholesky(factors, nrFrontals); toc(2, "EliminateCholesky"); } catch (const exception& e) { cout << "Exception in EliminateCholesky: " << e.what() << endl; SETDEBUG("EliminateCholesky", true); SETDEBUG("updateATA", true); SETDEBUG("JacobianFactor::eliminate", true); SETDEBUG("JacobianFactor::Combine", true); SETDEBUG("choleskyPartial", true); factors.print("Combining factors: "); EliminateCholesky(factors, nrFrontals); throw; } const bool checkCholesky = ISDEBUG("EliminateGaussian Check Cholesky"); if (checkCholesky) { GaussianFactorGraph::EliminationResult expected; FactorGraph jacobians = convertToJacobians(factors); try { // Compare with QR expected = EliminateJacobians(jacobians, nrFrontals); } catch (...) { cout << "Exception in QR" << endl; throw; } H actual_factor(*ret.second); H expected_factor(*expected.second); if (!assert_equal(*expected.first, *ret.first, 100.0) || !assert_equal( expected_factor, actual_factor, 1.0)) { cout << "Cholesky and QR do not agree" << endl; SETDEBUG("EliminateCholesky", true); SETDEBUG("updateATA", true); SETDEBUG("JacobianFactor::eliminate", true); SETDEBUG("JacobianFactor::Combine", true); jacobians.print("Jacobian Factors: "); EliminateJacobians(jacobians, nrFrontals); EliminateCholesky(factors, nrFrontals); factors.print("Combining factors: "); throw runtime_error("Cholesky and QR do not agree"); } } return ret; } // \EliminatePreferCholesky /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminateLDL( const FactorGraph& factors, size_t nrFrontals) { const bool debug = ISDEBUG("EliminateLDL"); // Find the scatter and variable dimensions tic(1, "find scatter"); Scatter scatter(findScatterAndDims(factors)); toc(1, "find scatter"); // Pull out keys and dimensions tic(2, "keys"); vector keys(scatter.size()); vector dimensions(scatter.size() + 1); BOOST_FOREACH(const Scatter::value_type& var_slot, scatter) { keys[var_slot.second.slot] = var_slot.first; dimensions[var_slot.second.slot] = var_slot.second.dimension; } // This is for the r.h.s. vector dimensions.back() = 1; toc(2, "keys"); // Form Ab' * Ab tic(3, "combine"); if (debug) { // print out everything before combine factors.print("Factors to be combined into hessian"); cout << "Dimensions (" << dimensions.size() << "): "; BOOST_FOREACH(size_t d, dimensions) cout << d << " "; cout << "\nScatter:" << endl; BOOST_FOREACH(const Scatter::value_type& p, scatter) cout << " Index: " << p.first << ", " << p.second.toString() << endl; } HessianFactor::shared_ptr // combinedFactor(new HessianFactor(factors, dimensions, scatter)); toc(3, "combine"); // Do LDL, note that after this, the lower triangle still contains // some untouched non-zeros that should be zero. We zero them while // extracting submatrices next. tic(4, "partial LDL"); Eigen::LDLT::TranspositionType permutation = combinedFactor->partialLDL(nrFrontals); toc(4, "partial LDL"); // Extract conditionals and fill in details of the remaining factor tic(5, "split"); GaussianConditional::shared_ptr conditionals = combinedFactor->splitEliminatedFactor(nrFrontals, keys, permutation); if (debug) { conditionals->print("Extracted conditionals: "); combinedFactor->print("Eliminated factor (L piece): "); } toc(5, "split"); combinedFactor->assertInvariants(); return make_pair(conditionals, combinedFactor); } // \EliminateLDL /* ************************************************************************* */ GaussianFactorGraph::EliminationResult EliminatePreferLDL( const FactorGraph& factors, size_t nrFrontals) { typedef JacobianFactor J; typedef HessianFactor H; // If any JacobianFactors have constrained noise models, we have to convert // all factors to JacobianFactors. Otherwise, we can convert all factors // to HessianFactors. This is because QR can handle constrained noise // models but LDL cannot. // Decide whether to use QR or LDL // Check if any JacobianFactors have constrained noise models. bool useQR = false; BOOST_FOREACH(const GaussianFactor::shared_ptr& factor, factors) { J::shared_ptr jacobian(boost::dynamic_pointer_cast(factor)); if (jacobian && jacobian->get_model()->isConstrained()) { useQR = true; break; } } // Convert all factors to the appropriate type // and call the type-specific EliminateGaussian. if (useQR) return EliminateQR(factors, nrFrontals); GaussianFactorGraph::EliminationResult ret; try { tic(2, "EliminateLDL"); ret = EliminateLDL(factors, nrFrontals); toc(2, "EliminateLDL"); } catch (const exception& e) { cout << "Exception in EliminateLDL: " << e.what() << endl; SETDEBUG("EliminateLDL", true); SETDEBUG("updateATA", true); SETDEBUG("JacobianFactor::eliminate", true); SETDEBUG("JacobianFactor::Combine", true); SETDEBUG("ldlPartial", true); SETDEBUG("findScatterAndDims", true); factors.print("Combining factors: "); EliminateLDL(factors, nrFrontals); throw; } const bool checkLDL = ISDEBUG("EliminateGaussian Check LDL"); if (checkLDL) { GaussianFactorGraph::EliminationResult expected; FactorGraph jacobians = convertToJacobians(factors); try { // Compare with QR expected = EliminateJacobians(jacobians, nrFrontals); } catch (...) { cout << "Exception in QR" << endl; throw; } H actual_factor(*ret.second); H expected_factor(*expected.second); if (!assert_equal(*expected.first, *ret.first, 100.0) || !assert_equal( expected_factor, actual_factor, 1.0)) { cout << "LDL and QR do not agree" << endl; SETDEBUG("EliminateLDL", true); SETDEBUG("updateATA", true); SETDEBUG("JacobianFactor::eliminate", true); SETDEBUG("JacobianFactor::Combine", true); jacobians.print("Jacobian Factors: "); EliminateJacobians(jacobians, nrFrontals); EliminateLDL(factors, nrFrontals); factors.print("Combining factors: "); throw runtime_error("LDL and QR do not agree"); } } return ret; } // \EliminatePreferLDL } // namespace gtsam