| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 | // Ceres Solver - A fast non-linear least squares minimizer// Copyright 2015 Google Inc. All rights reserved.// http://ceres-solver.org///// Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are met://// * Redistributions of source code must retain the above copyright notice,//   this list of conditions and the following disclaimer.// * Redistributions in binary form must reproduce the above copyright notice,//   this list of conditions and the following disclaimer in the documentation//   and/or other materials provided with the distribution.// * Neither the name of Google Inc. nor the names of its contributors may be//   used to endorse or promote products derived from this software without//   specific prior written permission.//// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE// POSSIBILITY OF SUCH DAMAGE.//// Author: sameeragarwal@google.com (Sameer Agarwal)#include "ceres/reorder_program.h"#include <algorithm>#include <memory>#include <numeric>#include <vector>#include "ceres/cxsparse.h"#include "ceres/internal/port.h"#include "ceres/ordered_groups.h"#include "ceres/parameter_block.h"#include "ceres/parameter_block_ordering.h"#include "ceres/problem_impl.h"#include "ceres/program.h"#include "ceres/residual_block.h"#include "ceres/solver.h"#include "ceres/suitesparse.h"#include "ceres/triplet_sparse_matrix.h"#include "ceres/types.h"#include "Eigen/SparseCore"#ifdef CERES_USE_EIGEN_SPARSE#include "Eigen/OrderingMethods"#endif#include "glog/logging.h"namespace ceres {namespace internal {using std::map;using std::set;using std::string;using std::vector;namespace {// Find the minimum index of any parameter block to the given// residual.  Parameter blocks that have indices greater than// size_of_first_elimination_group are considered to have an index// equal to size_of_first_elimination_group.static int MinParameterBlock(const ResidualBlock* residual_block,                             int size_of_first_elimination_group) {  int min_parameter_block_position = size_of_first_elimination_group;  for (int i = 0; i < residual_block->NumParameterBlocks(); ++i) {    ParameterBlock* parameter_block = residual_block->parameter_blocks()[i];    if (!parameter_block->IsConstant()) {      CHECK_NE(parameter_block->index(), -1)          << "Did you forget to call Program::SetParameterOffsetsAndIndex()? "          << "This is a Ceres bug; please contact the developers!";      min_parameter_block_position = std::min(parameter_block->index(),                                              min_parameter_block_position);    }  }  return min_parameter_block_position;}#if defined(CERES_USE_EIGEN_SPARSE)Eigen::SparseMatrix<int> CreateBlockJacobian(    const TripletSparseMatrix& block_jacobian_transpose) {  typedef Eigen::SparseMatrix<int> SparseMatrix;  typedef Eigen::Triplet<int> Triplet;  const int* rows = block_jacobian_transpose.rows();  const int* cols = block_jacobian_transpose.cols();  int num_nonzeros = block_jacobian_transpose.num_nonzeros();  vector<Triplet> triplets;  triplets.reserve(num_nonzeros);  for (int i = 0; i < num_nonzeros; ++i) {    triplets.push_back(Triplet(cols[i], rows[i], 1));  }  SparseMatrix block_jacobian(block_jacobian_transpose.num_cols(),                              block_jacobian_transpose.num_rows());  block_jacobian.setFromTriplets(triplets.begin(), triplets.end());  return block_jacobian;}#endifvoid OrderingForSparseNormalCholeskyUsingSuiteSparse(    const TripletSparseMatrix& tsm_block_jacobian_transpose,    const vector<ParameterBlock*>& parameter_blocks,    const ParameterBlockOrdering& parameter_block_ordering,    int* ordering) {#ifdef CERES_NO_SUITESPARSE  LOG(FATAL) << "Congratulations, you found a Ceres bug! "             << "Please report this error to the developers.";#else  SuiteSparse ss;  cholmod_sparse* block_jacobian_transpose =      ss.CreateSparseMatrix(          const_cast<TripletSparseMatrix*>(&tsm_block_jacobian_transpose));  // No CAMD or the user did not supply a useful ordering, then just  // use regular AMD.  if (parameter_block_ordering.NumGroups() <= 1 ||      !SuiteSparse::IsConstrainedApproximateMinimumDegreeOrderingAvailable()) {    ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]);  } else {    vector<int> constraints;    for (int i = 0; i < parameter_blocks.size(); ++i) {      constraints.push_back(          parameter_block_ordering.GroupId(              parameter_blocks[i]->mutable_user_state()));    }    // Renumber the entries of constraints to be contiguous integers    // as CAMD requires that the group ids be in the range [0,    // parameter_blocks.size() - 1].    MapValuesToContiguousRange(constraints.size(), &constraints[0]);    ss.ConstrainedApproximateMinimumDegreeOrdering(block_jacobian_transpose,                                                   &constraints[0],                                                   ordering);  }  VLOG(2) << "Block ordering stats: "          << " flops: " << ss.mutable_cc()->fl          << " lnz  : " << ss.mutable_cc()->lnz          << " anz  : " << ss.mutable_cc()->anz;  ss.Free(block_jacobian_transpose);#endif  // CERES_NO_SUITESPARSE}void OrderingForSparseNormalCholeskyUsingCXSparse(    const TripletSparseMatrix& tsm_block_jacobian_transpose,    int* ordering) {#ifdef CERES_NO_CXSPARSE  LOG(FATAL) << "Congratulations, you found a Ceres bug! "             << "Please report this error to the developers.";#else  // CERES_NO_CXSPARSE  // CXSparse works with J'J instead of J'. So compute the block  // sparsity for J'J and compute an approximate minimum degree  // ordering.  CXSparse cxsparse;  cs_di* block_jacobian_transpose;  block_jacobian_transpose =      cxsparse.CreateSparseMatrix(            const_cast<TripletSparseMatrix*>(&tsm_block_jacobian_transpose));  cs_di* block_jacobian = cxsparse.TransposeMatrix(block_jacobian_transpose);  cs_di* block_hessian =      cxsparse.MatrixMatrixMultiply(block_jacobian_transpose, block_jacobian);  cxsparse.Free(block_jacobian);  cxsparse.Free(block_jacobian_transpose);  cxsparse.ApproximateMinimumDegreeOrdering(block_hessian, ordering);  cxsparse.Free(block_hessian);#endif  // CERES_NO_CXSPARSE}void OrderingForSparseNormalCholeskyUsingEigenSparse(    const TripletSparseMatrix& tsm_block_jacobian_transpose,    int* ordering) {#ifndef CERES_USE_EIGEN_SPARSE  LOG(FATAL) <<      "SPARSE_NORMAL_CHOLESKY cannot be used with EIGEN_SPARSE "      "because Ceres was not built with support for "      "Eigen's SimplicialLDLT decomposition. "      "This requires enabling building with -DEIGENSPARSE=ON.";#else  // This conversion from a TripletSparseMatrix to a Eigen::Triplet  // matrix is unfortunate, but unavoidable for now. It is not a  // significant performance penalty in the grand scheme of  // things. The right thing to do here would be to get a compressed  // row sparse matrix representation of the jacobian and go from  // there. But that is a project for another day.  typedef Eigen::SparseMatrix<int> SparseMatrix;  const SparseMatrix block_jacobian =      CreateBlockJacobian(tsm_block_jacobian_transpose);  const SparseMatrix block_hessian =      block_jacobian.transpose() * block_jacobian;  Eigen::AMDOrdering<int> amd_ordering;  Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic, int> perm;  amd_ordering(block_hessian, perm);  for (int i = 0; i < block_hessian.rows(); ++i) {    ordering[i] = perm.indices()[i];  }#endif  // CERES_USE_EIGEN_SPARSE}}  // namespacebool ApplyOrdering(const ProblemImpl::ParameterMap& parameter_map,                   const ParameterBlockOrdering& ordering,                   Program* program,                   string* error) {  const int num_parameter_blocks =  program->NumParameterBlocks();  if (ordering.NumElements() != num_parameter_blocks) {    *error = StringPrintf("User specified ordering does not have the same "                          "number of parameters as the problem. The problem"                          "has %d blocks while the ordering has %d blocks.",                          num_parameter_blocks,                          ordering.NumElements());    return false;  }  vector<ParameterBlock*>* parameter_blocks =      program->mutable_parameter_blocks();  parameter_blocks->clear();  const map<int, set<double*>>& groups = ordering.group_to_elements();  for (const auto& p : groups) {    const set<double*>& group = p.second;    for (double* parameter_block_ptr : group) {      auto it = parameter_map.find(parameter_block_ptr);      if (it == parameter_map.end()) {        *error = StringPrintf("User specified ordering contains a pointer "                              "to a double that is not a parameter block in "                              "the problem. The invalid double is in group: %d",                              p.first);        return false;      }      parameter_blocks->push_back(it->second);    }  }  return true;}bool LexicographicallyOrderResidualBlocks(    const int size_of_first_elimination_group,    Program* program,    string* error) {  CHECK_GE(size_of_first_elimination_group, 1)      << "Congratulations, you found a Ceres bug! Please report this error "      << "to the developers.";  // Create a histogram of the number of residuals for each E block. There is an  // extra bucket at the end to catch all non-eliminated F blocks.  vector<int> residual_blocks_per_e_block(size_of_first_elimination_group + 1);  vector<ResidualBlock*>* residual_blocks = program->mutable_residual_blocks();  vector<int> min_position_per_residual(residual_blocks->size());  for (int i = 0; i < residual_blocks->size(); ++i) {    ResidualBlock* residual_block = (*residual_blocks)[i];    int position = MinParameterBlock(residual_block,                                     size_of_first_elimination_group);    min_position_per_residual[i] = position;    DCHECK_LE(position, size_of_first_elimination_group);    residual_blocks_per_e_block[position]++;  }  // Run a cumulative sum on the histogram, to obtain offsets to the start of  // each histogram bucket (where each bucket is for the residuals for that  // E-block).  vector<int> offsets(size_of_first_elimination_group + 1);  std::partial_sum(residual_blocks_per_e_block.begin(),                   residual_blocks_per_e_block.end(),                   offsets.begin());  CHECK_EQ(offsets.back(), residual_blocks->size())      << "Congratulations, you found a Ceres bug! Please report this error "      << "to the developers.";  CHECK(find(residual_blocks_per_e_block.begin(),             residual_blocks_per_e_block.end() - 1, 0) !=        residual_blocks_per_e_block.end())      << "Congratulations, you found a Ceres bug! Please report this error "      << "to the developers.";  // Fill in each bucket with the residual blocks for its corresponding E block.  // Each bucket is individually filled from the back of the bucket to the front  // of the bucket. The filling order among the buckets is dictated by the  // residual blocks. This loop uses the offsets as counters; subtracting one  // from each offset as a residual block is placed in the bucket. When the  // filling is finished, the offset pointerts should have shifted down one  // entry (this is verified below).  vector<ResidualBlock*> reordered_residual_blocks(      (*residual_blocks).size(), static_cast<ResidualBlock*>(NULL));  for (int i = 0; i < residual_blocks->size(); ++i) {    int bucket = min_position_per_residual[i];    // Decrement the cursor, which should now point at the next empty position.    offsets[bucket]--;    // Sanity.    CHECK(reordered_residual_blocks[offsets[bucket]] == NULL)        << "Congratulations, you found a Ceres bug! Please report this error "        << "to the developers.";    reordered_residual_blocks[offsets[bucket]] = (*residual_blocks)[i];  }  // Sanity check #1: The difference in bucket offsets should match the  // histogram sizes.  for (int i = 0; i < size_of_first_elimination_group; ++i) {    CHECK_EQ(residual_blocks_per_e_block[i], offsets[i + 1] - offsets[i])        << "Congratulations, you found a Ceres bug! Please report this error "        << "to the developers.";  }  // Sanity check #2: No NULL's left behind.  for (int i = 0; i < reordered_residual_blocks.size(); ++i) {    CHECK(reordered_residual_blocks[i] != NULL)        << "Congratulations, you found a Ceres bug! Please report this error "        << "to the developers.";  }  // Now that the residuals are collected by E block, swap them in place.  swap(*program->mutable_residual_blocks(), reordered_residual_blocks);  return true;}// Pre-order the columns corresponding to the schur complement if// possible.static void MaybeReorderSchurComplementColumnsUsingSuiteSparse(    const ParameterBlockOrdering& parameter_block_ordering,    Program* program) {#ifndef CERES_NO_SUITESPARSE  SuiteSparse ss;  if (!SuiteSparse::IsConstrainedApproximateMinimumDegreeOrderingAvailable()) {    return;  }  vector<int> constraints;  vector<ParameterBlock*>& parameter_blocks =      *(program->mutable_parameter_blocks());  for (int i = 0; i < parameter_blocks.size(); ++i) {    constraints.push_back(        parameter_block_ordering.GroupId(            parameter_blocks[i]->mutable_user_state()));  }  // Renumber the entries of constraints to be contiguous integers as  // CAMD requires that the group ids be in the range [0,  // parameter_blocks.size() - 1].  MapValuesToContiguousRange(constraints.size(), &constraints[0]);  // Compute a block sparse presentation of J'.  std::unique_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose(      program->CreateJacobianBlockSparsityTranspose());  cholmod_sparse* block_jacobian_transpose =      ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get());  vector<int> ordering(parameter_blocks.size(), 0);  ss.ConstrainedApproximateMinimumDegreeOrdering(block_jacobian_transpose,                                                 &constraints[0],                                                 &ordering[0]);  ss.Free(block_jacobian_transpose);  const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks);  for (int i = 0; i < program->NumParameterBlocks(); ++i) {    parameter_blocks[i] = parameter_blocks_copy[ordering[i]];  }  program->SetParameterOffsetsAndIndex();#endif}static void MaybeReorderSchurComplementColumnsUsingEigen(    const int size_of_first_elimination_group,    const ProblemImpl::ParameterMap& parameter_map,    Program* program) {#if defined(CERES_USE_EIGEN_SPARSE)  std::unique_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose(      program->CreateJacobianBlockSparsityTranspose());  typedef Eigen::SparseMatrix<int> SparseMatrix;  const SparseMatrix block_jacobian =      CreateBlockJacobian(*tsm_block_jacobian_transpose);  const int num_rows = block_jacobian.rows();  const int num_cols = block_jacobian.cols();  // Vertically partition the jacobian in parameter blocks of type E  // and F.  const SparseMatrix E =      block_jacobian.block(0,                           0,                           num_rows,                           size_of_first_elimination_group);  const SparseMatrix F =      block_jacobian.block(0,                           size_of_first_elimination_group,                           num_rows,                           num_cols - size_of_first_elimination_group);  // Block sparsity pattern of the schur complement.  const SparseMatrix block_schur_complement =      F.transpose() * F - F.transpose() * E * E.transpose() * F;  Eigen::AMDOrdering<int> amd_ordering;  Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic, int> perm;  amd_ordering(block_schur_complement, perm);  const vector<ParameterBlock*>& parameter_blocks = program->parameter_blocks();  vector<ParameterBlock*> ordering(num_cols);  // The ordering of the first size_of_first_elimination_group does  // not matter, so we preserve the existing ordering.  for (int i = 0; i < size_of_first_elimination_group; ++i) {    ordering[i] = parameter_blocks[i];  }  // For the rest of the blocks, use the ordering computed using AMD.  for (int i = 0; i < block_schur_complement.cols(); ++i) {    ordering[size_of_first_elimination_group + i] =        parameter_blocks[size_of_first_elimination_group + perm.indices()[i]];  }  swap(*program->mutable_parameter_blocks(), ordering);  program->SetParameterOffsetsAndIndex();#endif}bool ReorderProgramForSchurTypeLinearSolver(    const LinearSolverType linear_solver_type,    const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,    const ProblemImpl::ParameterMap& parameter_map,    ParameterBlockOrdering* parameter_block_ordering,    Program* program,    string* error) {  if (parameter_block_ordering->NumElements() !=      program->NumParameterBlocks()) {    *error = StringPrintf(        "The program has %d parameter blocks, but the parameter block "        "ordering has %d parameter blocks.",        program->NumParameterBlocks(),        parameter_block_ordering->NumElements());    return false;  }  if (parameter_block_ordering->NumGroups() == 1) {    // If the user supplied an parameter_block_ordering with just one    // group, it is equivalent to the user supplying NULL as an    // parameter_block_ordering. Ceres is completely free to choose the    // parameter block ordering as it sees fit. For Schur type solvers,    // this means that the user wishes for Ceres to identify the    // e_blocks, which we do by computing a maximal independent set.    vector<ParameterBlock*> schur_ordering;    const int size_of_first_elimination_group =        ComputeStableSchurOrdering(*program, &schur_ordering);    CHECK_EQ(schur_ordering.size(), program->NumParameterBlocks())        << "Congratulations, you found a Ceres bug! Please report this error "        << "to the developers.";    // Update the parameter_block_ordering object.    for (int i = 0; i < schur_ordering.size(); ++i) {      double* parameter_block = schur_ordering[i]->mutable_user_state();      const int group_id = (i < size_of_first_elimination_group) ? 0 : 1;      parameter_block_ordering->AddElementToGroup(parameter_block, group_id);    }    // We could call ApplyOrdering but this is cheaper and    // simpler.    swap(*program->mutable_parameter_blocks(), schur_ordering);  } else {    // The user provided an ordering with more than one elimination    // group.    // Verify that the first elimination group is an independent set.    const set<double*>& first_elimination_group =        parameter_block_ordering        ->group_to_elements()        .begin()        ->second;    if (!program->IsParameterBlockSetIndependent(first_elimination_group)) {      *error =          StringPrintf("The first elimination group in the parameter block "                       "ordering of size %zd is not an independent set",                       first_elimination_group.size());      return false;    }    if (!ApplyOrdering(parameter_map,                       *parameter_block_ordering,                       program,                       error)) {      return false;    }  }  program->SetParameterOffsetsAndIndex();  const int size_of_first_elimination_group =      parameter_block_ordering->group_to_elements().begin()->second.size();  if (linear_solver_type == SPARSE_SCHUR) {    if (sparse_linear_algebra_library_type == SUITE_SPARSE) {      MaybeReorderSchurComplementColumnsUsingSuiteSparse(          *parameter_block_ordering,          program);    } else if (sparse_linear_algebra_library_type == EIGEN_SPARSE) {      MaybeReorderSchurComplementColumnsUsingEigen(          size_of_first_elimination_group,          parameter_map,          program);    }  }  // Schur type solvers also require that their residual blocks be  // lexicographically ordered.  return LexicographicallyOrderResidualBlocks(      size_of_first_elimination_group, program, error);}bool ReorderProgramForSparseCholesky(    const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,    const ParameterBlockOrdering& parameter_block_ordering,    int start_row_block,    Program* program,    string* error) {  if (parameter_block_ordering.NumElements() != program->NumParameterBlocks()) {    *error = StringPrintf(        "The program has %d parameter blocks, but the parameter block "        "ordering has %d parameter blocks.",        program->NumParameterBlocks(),        parameter_block_ordering.NumElements());    return false;  }  // Compute a block sparse presentation of J'.  std::unique_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose(      program->CreateJacobianBlockSparsityTranspose(start_row_block));  vector<int> ordering(program->NumParameterBlocks(), 0);  vector<ParameterBlock*>& parameter_blocks =      *(program->mutable_parameter_blocks());  if (sparse_linear_algebra_library_type == SUITE_SPARSE) {    OrderingForSparseNormalCholeskyUsingSuiteSparse(        *tsm_block_jacobian_transpose,        parameter_blocks,        parameter_block_ordering,        &ordering[0]);  } else if (sparse_linear_algebra_library_type == CX_SPARSE) {    OrderingForSparseNormalCholeskyUsingCXSparse(        *tsm_block_jacobian_transpose,        &ordering[0]);  } else if (sparse_linear_algebra_library_type == ACCELERATE_SPARSE) {    // Accelerate does not provide a function to perform reordering without    // performing a full symbolic factorisation.  As such, we have nothing    // to gain from trying to reorder the problem here, as it will happen    // in AppleAccelerateCholesky::Factorize() (once) and reordering here    // would involve performing two symbolic factorisations instead of one    // which would have a negative overall impact on performance.    return true;  } else if (sparse_linear_algebra_library_type == EIGEN_SPARSE) {    OrderingForSparseNormalCholeskyUsingEigenSparse(        *tsm_block_jacobian_transpose,        &ordering[0]);  }  // Apply ordering.  const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks);  for (int i = 0; i < program->NumParameterBlocks(); ++i) {    parameter_blocks[i] = parameter_blocks_copy[ordering[i]];  }  program->SetParameterOffsetsAndIndex();  return true;}int ReorderResidualBlocksByPartition(    const std::unordered_set<ResidualBlockId>& bottom_residual_blocks,    Program* program) {  auto residual_blocks = program->mutable_residual_blocks();  auto it = std::partition(      residual_blocks->begin(), residual_blocks->end(),      [&bottom_residual_blocks](ResidualBlock* r) {        return bottom_residual_blocks.count(r) == 0;      });  return it - residual_blocks->begin();}}  // namespace internal}  // namespace ceres
 |