/* ---------------------------------------------------------------------------- * 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 JacobianFactor.cpp * @author Richard Roberts * @date Dec 8, 2010 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost::lambda; namespace gtsam { /* ************************************************************************* */ void JacobianFactor::assertInvariants() const { #ifndef NDEBUG GaussianFactor::assertInvariants(); // The base class checks for unique keys assert((size() == 0 && Ab_.rows() == 0 && Ab_.nBlocks() == 0) || size()+1 == Ab_.nBlocks()); assert(firstNonzeroBlocks_.size() == Ab_.rows()); for(size_t i=0; i > &terms, const Vector &b, const SharedDiagonal& model) : GaussianFactor(GetKeys(terms.size(), terms.begin(), terms.end())), model_(model), firstNonzeroBlocks_(b.size(), 0), Ab_(matrix_) { size_t* dims = (size_t*)alloca(sizeof(size_t)*(terms.size()+1)); // FIXME: alloca is bad, just ask Google. for(size_t j=0; j > &terms, const Vector &b, const SharedDiagonal& model) : GaussianFactor(GetKeys(terms.size(), terms.begin(), terms.end())), model_(model), firstNonzeroBlocks_(b.size(), 0), Ab_(matrix_) { size_t* dims=(size_t*)alloca(sizeof(size_t)*(terms.size()+1)); // FIXME: alloca is bad, just ask Google. size_t j=0; std::list >::const_iterator term=terms.begin(); for(; term!=terms.end(); ++term,++j) dims[j] = term->second.cols(); dims[j] = 1; Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+terms.size()+1, b.size())); j = 0; for(term=terms.begin(); term!=terms.end(); ++term,++j) Ab_(j) = term->second; getb() = b; assertInvariants(); } /* ************************************************************************* */ JacobianFactor::JacobianFactor(const GaussianConditional& cg) : GaussianFactor(cg), model_(noiseModel::Diagonal::Sigmas(cg.get_sigmas(), true)), Ab_(matrix_) { Ab_.assignNoalias(cg.rsd_); firstNonzeroBlocks_.resize(cg.get_d().size(), 0); // set sigmas from precisions assertInvariants(); } /* ************************************************************************* */ JacobianFactor::JacobianFactor(const HessianFactor& factor) : Ab_(matrix_) { keys_ = factor.keys_; Ab_.assignNoalias(factor.info_); size_t maxrank; try { maxrank = choleskyCareful(matrix_).first; } catch(const CarefulCholeskyNegativeMatrixException& e) { cout << "Attempting to convert a HessianFactor to a JacobianFactor, but for this\n" "HessianFactor it is not possible because either the Hessian is negative or\n" "indefinite, or the quadratic error function it describes becomes negative for\n" "some values. Here is the HessianFactor on which this conversion was attempted:\n"; factor.print(""); throw; } // Zero out lower triangle matrix_.topRows(maxrank).triangularView() = Matrix::Zero(maxrank, matrix_.cols()); // FIXME: replace with triangular system Ab_.rowEnd() = maxrank; model_ = noiseModel::Unit::Create(maxrank); firstNonzeroBlocks_.resize(this->rows(), 0); // Sort keys set vars; for(size_t j=0; jBase::operator=(rhs); // Copy keys model_ = rhs.model_; // Copy noise model firstNonzeroBlocks_ = rhs.firstNonzeroBlocks_; // Copy staircase pattern Ab_.assignNoalias(rhs.Ab_); // Copy matrix and block structure assertInvariants(); return *this; } /* ************************************************************************* */ void JacobianFactor::print(const string& s) const { cout << s << "\n"; if (empty()) { cout << " empty, keys: "; BOOST_FOREACH(const Index& key, keys()) { cout << key << " "; } cout << endl; } else { for(const_iterator key=begin(); key!=end(); ++key) cout << boost::format("A[%1%]=\n")%*key << getA(key) << endl; cout << "b=" << getb() << endl; model_->print("model"); } } /* ************************************************************************* */ // Check if two linear factors are equal bool JacobianFactor::equals(const GaussianFactor& f_, double tol) const { if(!dynamic_cast(&f_)) return false; else { const JacobianFactor& f(static_cast(f_)); if (empty()) return (f.empty()); if(keys()!=f.keys() /*|| !model_->equals(lf->model_, tol)*/) return false; if (!(Ab_.rows() == f.Ab_.rows() && Ab_.cols() == f.Ab_.cols())) return false; constABlock Ab1(Ab_.range(0, Ab_.nBlocks())); constABlock Ab2(f.Ab_.range(0, f.Ab_.nBlocks())); for(size_t row=0; row< (size_t) Ab1.rows(); ++row) if(!equal_with_abs_tol(Ab1.row(row), Ab2.row(row), tol) && !equal_with_abs_tol(-Ab1.row(row), Ab2.row(row), tol)) return false; return true; } } /* ************************************************************************* */ Vector JacobianFactor::unweighted_error(const VectorValues& c) const { Vector e = -getb(); if (empty()) return e; for(size_t pos=0; poswhiten(-getb()); return model_->whiten(unweighted_error(c)); } /* ************************************************************************* */ double JacobianFactor::error(const VectorValues& c) const { if (empty()) return 0; Vector weighted = error_vector(c); return 0.5 * weighted.dot(weighted); } /* ************************************************************************* */ Matrix JacobianFactor::computeInformation() const { Matrix AbWhitened = Ab_.full(); model_->WhitenInPlace(AbWhitened); return AbWhitened.transpose() * AbWhitened; } /* ************************************************************************* */ Vector JacobianFactor::operator*(const VectorValues& x) const { Vector Ax = zero(Ab_.rows()); if (empty()) return Ax; // Just iterate over all A matrices and multiply in correct config part for(size_t pos=0; poswhiten(Ax); } /* ************************************************************************* */ void JacobianFactor::transposeMultiplyAdd(double alpha, const Vector& e, VectorValues& x) const { Vector E = alpha * model_->whiten(e); // Just iterate over all A matrices and insert Ai^e into VectorValues for(size_t pos=0; pos JacobianFactor::matrix(bool weight) const { Matrix A(Ab_.range(0, size())); Vector b(getb()); // divide in sigma so error is indeed 0.5*|Ax-b| if (weight) model_->WhitenSystem(A,b); return make_pair(A, b); } /* ************************************************************************* */ Matrix JacobianFactor::matrix_augmented(bool weight) const { if (weight) { Matrix Ab(Ab_.range(0,Ab_.nBlocks())); model_->WhitenInPlace(Ab); return Ab; } else return Ab_.range(0, Ab_.nBlocks()); } /* ************************************************************************* */ std::vector > JacobianFactor::sparse(const std::vector& columnIndices) const { std::vector > entries; // iterate over all variables in the factor for(const_iterator var=begin(); varWhiten(getA(var))); // find first column index for this key size_t column_start = columnIndices[*var]; for (size_t i = 0; i < (size_t) whitenedA.rows(); i++) for (size_t j = 0; j < (size_t) whitenedA.cols(); j++) { double s = whitenedA(i,j); if (std::abs(s) > 1e-12) entries.push_back( boost::make_tuple(i, column_start + j, s)); } } Vector whitenedb(model_->whiten(getb())); size_t bcolumn = columnIndices.back(); for (size_t i = 0; i < (size_t) whitenedb.size(); i++) entries.push_back(boost::make_tuple(i, bcolumn, whitenedb(i))); // return the result return entries; } /* ************************************************************************* */ JacobianFactor JacobianFactor::whiten() const { JacobianFactor result(*this); result.model_->WhitenInPlace(result.matrix_); result.model_ = noiseModel::Unit::Create(result.model_->dim()); return result; } /* ************************************************************************* */ GaussianConditional::shared_ptr JacobianFactor::eliminateFirst() { return this->eliminate(1); } /* ************************************************************************* */ GaussianConditional::shared_ptr JacobianFactor::eliminate(size_t nrFrontals) { assert(Ab_.rowStart() == 0 && Ab_.rowEnd() == (size_t) matrix_.rows() && Ab_.firstBlock() == 0); assert(size() >= nrFrontals); assertInvariants(); const bool debug = ISDEBUG("JacobianFactor::eliminate"); if(debug) cout << "Eliminating " << nrFrontals << " frontal variables" << endl; if(debug) this->print("Eliminating JacobianFactor: "); // NOTE: stairs are not currently used in the Eigen QR implementation // add this back if DenseQR is ever reimplemented // tic(1, "stairs"); // // Translate the left-most nonzero column indices into top-most zero row indices // vector firstZeroRows(Ab_.cols()); // { // size_t lastNonzeroRow = 0; // vector::iterator firstZeroRowsIt = firstZeroRows.begin(); // for(size_t var=0; varrows() && firstNonzeroBlocks_[lastNonzeroRow] <= var) // ++ lastNonzeroRow; // fill(firstZeroRowsIt, firstZeroRowsIt+Ab_(var).cols(), lastNonzeroRow); // firstZeroRowsIt += Ab_(var).cols(); // } // assert(firstZeroRowsIt+1 == firstZeroRows.end()); // *firstZeroRowsIt = this->rows(); // } // toc(1, "stairs"); // #ifndef NDEBUG // for(size_t col=0; colQR(matrix_); toc(2, "QR"); // Zero the lower-left triangle. todo: not all of these entries actually // need to be zeroed if we are careful to start copying rows after the last // structural zero. if(matrix_.rows() > 0) for(size_t j=0; j<(size_t) matrix_.cols(); ++j) for(size_t i=j+1; idim(); ++i) matrix_(i,j) = 0.0; if(debug) gtsam::print(matrix_, "QR result: "); if(debug) noiseModel->print("QR result noise model: "); // Check for singular factor if(noiseModel->dim() < frontalDim) { throw domain_error((boost::format( "JacobianFactor is singular in variable %1%, discovered while attempting\n" "to eliminate this variable.") % front()).str()); } // Extract conditionals tic(3, "cond Rd"); GaussianConditional::shared_ptr conditionals(new GaussianConditional()); // Restrict the matrix to be in the first nrFrontals variables Ab_.rowEnd() = Ab_.rowStart() + frontalDim; const Eigen::VectorBlock sigmas = noiseModel->sigmas().segment(Ab_.rowStart(), Ab_.rowEnd()-Ab_.rowStart()); conditionals = boost::make_shared(begin(), end(), nrFrontals, Ab_, sigmas); if(debug) conditionals->print("Extracted conditional: "); Ab_.rowStart() += frontalDim; Ab_.firstBlock() += nrFrontals; toc(3, "cond Rd"); if(debug) conditionals->print("Extracted conditionals: "); tic(4, "remaining factor"); // Take lower-right block of Ab to get the new factor Ab_.rowEnd() = noiseModel->dim(); keys_.erase(begin(), begin() + nrFrontals); // Set sigmas with the right model if (noiseModel->isConstrained()) model_ = noiseModel::Constrained::MixedSigmas(sub(noiseModel->sigmas(), frontalDim, noiseModel->dim())); else model_ = noiseModel::Diagonal::Sigmas(sub(noiseModel->sigmas(), frontalDim, noiseModel->dim())); if(debug) this->print("Eliminated factor: "); assert(Ab_.rows() <= Ab_.cols()-1); toc(4, "remaining factor"); // todo SL: deal with "dead" pivot columns!!! tic(5, "rowstarts"); size_t varpos = 0; firstNonzeroBlocks_.resize(this->rows()); for(size_t row=0; row& rowSources) const { assertInvariants(); for (size_t row = 0; row < rows(); ++row) { Index firstNonzeroVar; if (firstNonzeroBlocks_[row] < size()) { firstNonzeroVar = keys_[firstNonzeroBlocks_[row]]; } else if (firstNonzeroBlocks_[row] == size()) { firstNonzeroVar = back() + 1; } else { assert(false); } rowSources.push_back(_RowSource(firstNonzeroVar, index, row)); } } /* ************************************************************************* */ void JacobianFactor::allocate(const VariableSlots& variableSlots, vector< size_t>& varDims, size_t m) { keys_.resize(variableSlots.size()); std::transform(variableSlots.begin(), variableSlots.end(), begin(), boost::bind(&VariableSlots::const_iterator::value_type::first, boost::lambda::_1)); varDims.push_back(1); Ab_.copyStructureFrom(BlockAb(matrix_, varDims.begin(), varDims.end(), m)); firstNonzeroBlocks_.resize(m); } /* ************************************************************************* */ void JacobianFactor::copyRow(const JacobianFactor& source, Index sourceRow, size_t sourceSlot, size_t row, Index slot) { ABlock combinedBlock = Ab_(slot); if (sourceSlot != numeric_limits::max()) { if (source.firstNonzeroBlocks_[sourceRow] <= sourceSlot) { const constABlock sourceBlock(source.Ab_(sourceSlot)); combinedBlock.row(row).noalias() = sourceBlock.row(sourceRow); } else { combinedBlock.row(row).setZero(); } } else { combinedBlock.row(row).setZero(); } } /* ************************************************************************* */ void JacobianFactor::copyFNZ(size_t m, size_t n, vector<_RowSource>& rowSources) { Index i = 0; for (size_t row = 0; row < m; ++row) { while (i < n && rowSources[row].firstNonzeroVar > keys_[i]) ++i; firstNonzeroBlocks_[row] = i; } } /* ************************************************************************* */ void JacobianFactor::setModel(bool anyConstrained, const Vector& sigmas) { if (anyConstrained) model_ = noiseModel::Constrained::MixedSigmas(sigmas); else model_ = noiseModel::Diagonal::Sigmas(sigmas); } /* ************************************************************************* */ Errors operator*(const FactorGraph& fg, const VectorValues& x) { Errors e; BOOST_FOREACH(const JacobianFactor::shared_ptr& Ai, fg) { e.push_back((*Ai)*x); } return e; } /* ************************************************************************* */ void multiplyInPlace(const FactorGraph& fg, const VectorValues& x, Errors& e) { multiplyInPlace(fg,x,e.begin()); } /* ************************************************************************* */ void multiplyInPlace(const FactorGraph& fg, const VectorValues& x, const Errors::iterator& e) { Errors::iterator ei = e; BOOST_FOREACH(const JacobianFactor::shared_ptr& Ai, fg) { *ei = (*Ai)*x; ei++; } } /* ************************************************************************* */ // x += alpha*A'*e void transposeMultiplyAdd(const FactorGraph& fg, double alpha, const Errors& e, VectorValues& x) { // For each factor add the gradient contribution Errors::const_iterator ei = e.begin(); BOOST_FOREACH(const JacobianFactor::shared_ptr& Ai, fg) { Ai->transposeMultiplyAdd(alpha,*(ei++),x); } } /* ************************************************************************* */ VectorValues gradient(const FactorGraph& fg, const VectorValues& x0) { // It is crucial for performance to make a zero-valued clone of x VectorValues g = VectorValues::Zero(x0); Errors e; BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, fg) { e.push_back(factor->error_vector(x0)); } transposeMultiplyAdd(fg, 1.0, e, g); return g; } /* ************************************************************************* */ void gradientAtZero(const FactorGraph& fg, VectorValues& g) { // Zero-out the gradient g.setZero(); Errors e; BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, fg) { e.push_back(-factor->getb()); } transposeMultiplyAdd(fg, 1.0, e, g); } /* ************************************************************************* */ void residual(const FactorGraph& fg, const VectorValues &x, VectorValues &r) { Index i = 0 ; BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, fg) { r[i] = factor->getb(); i++; } VectorValues Ax = VectorValues::SameStructure(r); multiply(fg,x,Ax); axpy(-1.0,Ax,r); } /* ************************************************************************* */ void multiply(const FactorGraph& fg, const VectorValues &x, VectorValues &r) { r.vector() = Vector::Zero(r.dim()); Index i = 0; BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, fg) { SubVector &y = r[i]; for(JacobianFactor::const_iterator j = factor->begin(); j != factor->end(); ++j) { y += factor->getA(j) * x[*j]; } ++i; } } /* ************************************************************************* */ void transposeMultiply(const FactorGraph& fg, const VectorValues &r, VectorValues &x) { x.vector() = Vector::Zero(x.dim()); Index i = 0; BOOST_FOREACH(const JacobianFactor::shared_ptr& factor, fg) { for(JacobianFactor::const_iterator j = factor->begin(); j != factor->end(); ++j) { x[*j] += factor->getA(j).transpose() * r[i]; } ++i; } } }