1// Ceres Solver - A fast non-linear least squares minimizer 2// Copyright 2014 Google Inc. All rights reserved. 3// http://code.google.com/p/ceres-solver/ 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are met: 7// 8// * Redistributions of source code must retain the above copyright notice, 9// this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above copyright notice, 11// this list of conditions and the following disclaimer in the documentation 12// and/or other materials provided with the distribution. 13// * Neither the name of Google Inc. nor the names of its contributors may be 14// used to endorse or promote products derived from this software without 15// specific prior written permission. 16// 17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27// POSSIBILITY OF SUCH DAMAGE. 28// 29// Author: sameeragarwal@google.com (Sameer Agarwal) 30 31#include "ceres/reorder_program.h" 32 33#include <algorithm> 34#include <numeric> 35#include <vector> 36 37#include "ceres/cxsparse.h" 38#include "ceres/internal/port.h" 39#include "ceres/ordered_groups.h" 40#include "ceres/parameter_block.h" 41#include "ceres/parameter_block_ordering.h" 42#include "ceres/problem_impl.h" 43#include "ceres/program.h" 44#include "ceres/program.h" 45#include "ceres/residual_block.h" 46#include "ceres/solver.h" 47#include "ceres/suitesparse.h" 48#include "ceres/triplet_sparse_matrix.h" 49#include "ceres/types.h" 50#include "glog/logging.h" 51 52namespace ceres { 53namespace internal { 54namespace { 55 56// Find the minimum index of any parameter block to the given residual. 57// Parameter blocks that have indices greater than num_eliminate_blocks are 58// considered to have an index equal to num_eliminate_blocks. 59static int MinParameterBlock(const ResidualBlock* residual_block, 60 int num_eliminate_blocks) { 61 int min_parameter_block_position = num_eliminate_blocks; 62 for (int i = 0; i < residual_block->NumParameterBlocks(); ++i) { 63 ParameterBlock* parameter_block = residual_block->parameter_blocks()[i]; 64 if (!parameter_block->IsConstant()) { 65 CHECK_NE(parameter_block->index(), -1) 66 << "Did you forget to call Program::SetParameterOffsetsAndIndex()? " 67 << "This is a Ceres bug; please contact the developers!"; 68 min_parameter_block_position = std::min(parameter_block->index(), 69 min_parameter_block_position); 70 } 71 } 72 return min_parameter_block_position; 73} 74 75void OrderingForSparseNormalCholeskyUsingSuiteSparse( 76 const TripletSparseMatrix& tsm_block_jacobian_transpose, 77 const vector<ParameterBlock*>& parameter_blocks, 78 const ParameterBlockOrdering& parameter_block_ordering, 79 int* ordering) { 80#ifdef CERES_NO_SUITESPARSE 81 LOG(FATAL) << "Congratulations, you found a Ceres bug! " 82 << "Please report this error to the developers."; 83#else 84 SuiteSparse ss; 85 cholmod_sparse* block_jacobian_transpose = 86 ss.CreateSparseMatrix( 87 const_cast<TripletSparseMatrix*>(&tsm_block_jacobian_transpose)); 88 89 // No CAMD or the user did not supply a useful ordering, then just 90 // use regular AMD. 91 if (parameter_block_ordering.NumGroups() <= 1 || 92 !SuiteSparse::IsConstrainedApproximateMinimumDegreeOrderingAvailable()) { 93 ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]); 94 } else { 95 vector<int> constraints; 96 for (int i = 0; i < parameter_blocks.size(); ++i) { 97 constraints.push_back( 98 parameter_block_ordering.GroupId( 99 parameter_blocks[i]->mutable_user_state())); 100 } 101 ss.ConstrainedApproximateMinimumDegreeOrdering(block_jacobian_transpose, 102 &constraints[0], 103 ordering); 104 } 105 106 ss.Free(block_jacobian_transpose); 107#endif // CERES_NO_SUITESPARSE 108} 109 110void OrderingForSparseNormalCholeskyUsingCXSparse( 111 const TripletSparseMatrix& tsm_block_jacobian_transpose, 112 int* ordering) { 113#ifdef CERES_NO_CXSPARSE 114 LOG(FATAL) << "Congratulations, you found a Ceres bug! " 115 << "Please report this error to the developers."; 116#else // CERES_NO_CXSPARSE 117 // CXSparse works with J'J instead of J'. So compute the block 118 // sparsity for J'J and compute an approximate minimum degree 119 // ordering. 120 CXSparse cxsparse; 121 cs_di* block_jacobian_transpose; 122 block_jacobian_transpose = 123 cxsparse.CreateSparseMatrix( 124 const_cast<TripletSparseMatrix*>(&tsm_block_jacobian_transpose)); 125 cs_di* block_jacobian = cxsparse.TransposeMatrix(block_jacobian_transpose); 126 cs_di* block_hessian = 127 cxsparse.MatrixMatrixMultiply(block_jacobian_transpose, block_jacobian); 128 cxsparse.Free(block_jacobian); 129 cxsparse.Free(block_jacobian_transpose); 130 131 cxsparse.ApproximateMinimumDegreeOrdering(block_hessian, ordering); 132 cxsparse.Free(block_hessian); 133#endif // CERES_NO_CXSPARSE 134} 135 136} // namespace 137 138bool ApplyOrdering(const ProblemImpl::ParameterMap& parameter_map, 139 const ParameterBlockOrdering& ordering, 140 Program* program, 141 string* error) { 142 const int num_parameter_blocks = program->NumParameterBlocks(); 143 if (ordering.NumElements() != num_parameter_blocks) { 144 *error = StringPrintf("User specified ordering does not have the same " 145 "number of parameters as the problem. The problem" 146 "has %d blocks while the ordering has %d blocks.", 147 num_parameter_blocks, 148 ordering.NumElements()); 149 return false; 150 } 151 152 vector<ParameterBlock*>* parameter_blocks = 153 program->mutable_parameter_blocks(); 154 parameter_blocks->clear(); 155 156 const map<int, set<double*> >& groups = 157 ordering.group_to_elements(); 158 159 for (map<int, set<double*> >::const_iterator group_it = groups.begin(); 160 group_it != groups.end(); 161 ++group_it) { 162 const set<double*>& group = group_it->second; 163 for (set<double*>::const_iterator parameter_block_ptr_it = group.begin(); 164 parameter_block_ptr_it != group.end(); 165 ++parameter_block_ptr_it) { 166 ProblemImpl::ParameterMap::const_iterator parameter_block_it = 167 parameter_map.find(*parameter_block_ptr_it); 168 if (parameter_block_it == parameter_map.end()) { 169 *error = StringPrintf("User specified ordering contains a pointer " 170 "to a double that is not a parameter block in " 171 "the problem. The invalid double is in group: %d", 172 group_it->first); 173 return false; 174 } 175 parameter_blocks->push_back(parameter_block_it->second); 176 } 177 } 178 return true; 179} 180 181bool LexicographicallyOrderResidualBlocks(const int num_eliminate_blocks, 182 Program* program, 183 string* error) { 184 CHECK_GE(num_eliminate_blocks, 1) 185 << "Congratulations, you found a Ceres bug! Please report this error " 186 << "to the developers."; 187 188 // Create a histogram of the number of residuals for each E block. There is an 189 // extra bucket at the end to catch all non-eliminated F blocks. 190 vector<int> residual_blocks_per_e_block(num_eliminate_blocks + 1); 191 vector<ResidualBlock*>* residual_blocks = program->mutable_residual_blocks(); 192 vector<int> min_position_per_residual(residual_blocks->size()); 193 for (int i = 0; i < residual_blocks->size(); ++i) { 194 ResidualBlock* residual_block = (*residual_blocks)[i]; 195 int position = MinParameterBlock(residual_block, num_eliminate_blocks); 196 min_position_per_residual[i] = position; 197 DCHECK_LE(position, num_eliminate_blocks); 198 residual_blocks_per_e_block[position]++; 199 } 200 201 // Run a cumulative sum on the histogram, to obtain offsets to the start of 202 // each histogram bucket (where each bucket is for the residuals for that 203 // E-block). 204 vector<int> offsets(num_eliminate_blocks + 1); 205 std::partial_sum(residual_blocks_per_e_block.begin(), 206 residual_blocks_per_e_block.end(), 207 offsets.begin()); 208 CHECK_EQ(offsets.back(), residual_blocks->size()) 209 << "Congratulations, you found a Ceres bug! Please report this error " 210 << "to the developers."; 211 212 CHECK(find(residual_blocks_per_e_block.begin(), 213 residual_blocks_per_e_block.end() - 1, 0) != 214 residual_blocks_per_e_block.end()) 215 << "Congratulations, you found a Ceres bug! Please report this error " 216 << "to the developers."; 217 218 // Fill in each bucket with the residual blocks for its corresponding E block. 219 // Each bucket is individually filled from the back of the bucket to the front 220 // of the bucket. The filling order among the buckets is dictated by the 221 // residual blocks. This loop uses the offsets as counters; subtracting one 222 // from each offset as a residual block is placed in the bucket. When the 223 // filling is finished, the offset pointerts should have shifted down one 224 // entry (this is verified below). 225 vector<ResidualBlock*> reordered_residual_blocks( 226 (*residual_blocks).size(), static_cast<ResidualBlock*>(NULL)); 227 for (int i = 0; i < residual_blocks->size(); ++i) { 228 int bucket = min_position_per_residual[i]; 229 230 // Decrement the cursor, which should now point at the next empty position. 231 offsets[bucket]--; 232 233 // Sanity. 234 CHECK(reordered_residual_blocks[offsets[bucket]] == NULL) 235 << "Congratulations, you found a Ceres bug! Please report this error " 236 << "to the developers."; 237 238 reordered_residual_blocks[offsets[bucket]] = (*residual_blocks)[i]; 239 } 240 241 // Sanity check #1: The difference in bucket offsets should match the 242 // histogram sizes. 243 for (int i = 0; i < num_eliminate_blocks; ++i) { 244 CHECK_EQ(residual_blocks_per_e_block[i], offsets[i + 1] - offsets[i]) 245 << "Congratulations, you found a Ceres bug! Please report this error " 246 << "to the developers."; 247 } 248 // Sanity check #2: No NULL's left behind. 249 for (int i = 0; i < reordered_residual_blocks.size(); ++i) { 250 CHECK(reordered_residual_blocks[i] != NULL) 251 << "Congratulations, you found a Ceres bug! Please report this error " 252 << "to the developers."; 253 } 254 255 // Now that the residuals are collected by E block, swap them in place. 256 swap(*program->mutable_residual_blocks(), reordered_residual_blocks); 257 return true; 258} 259 260void MaybeReorderSchurComplementColumnsUsingSuiteSparse( 261 const ParameterBlockOrdering& parameter_block_ordering, 262 Program* program) { 263 // Pre-order the columns corresponding to the schur complement if 264 // possible. 265#ifndef CERES_NO_SUITESPARSE 266 SuiteSparse ss; 267 if (!SuiteSparse::IsConstrainedApproximateMinimumDegreeOrderingAvailable()) { 268 return; 269 } 270 271 vector<int> constraints; 272 vector<ParameterBlock*>& parameter_blocks = 273 *(program->mutable_parameter_blocks()); 274 275 for (int i = 0; i < parameter_blocks.size(); ++i) { 276 constraints.push_back( 277 parameter_block_ordering.GroupId( 278 parameter_blocks[i]->mutable_user_state())); 279 } 280 281 // Renumber the entries of constraints to be contiguous integers 282 // as camd requires that the group ids be in the range [0, 283 // parameter_blocks.size() - 1]. 284 MapValuesToContiguousRange(constraints.size(), &constraints[0]); 285 286 // Set the offsets and index for CreateJacobianSparsityTranspose. 287 program->SetParameterOffsetsAndIndex(); 288 // Compute a block sparse presentation of J'. 289 scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose( 290 program->CreateJacobianBlockSparsityTranspose()); 291 292 293 cholmod_sparse* block_jacobian_transpose = 294 ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get()); 295 296 vector<int> ordering(parameter_blocks.size(), 0); 297 ss.ConstrainedApproximateMinimumDegreeOrdering(block_jacobian_transpose, 298 &constraints[0], 299 &ordering[0]); 300 ss.Free(block_jacobian_transpose); 301 302 const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks); 303 for (int i = 0; i < program->NumParameterBlocks(); ++i) { 304 parameter_blocks[i] = parameter_blocks_copy[ordering[i]]; 305 } 306#endif 307} 308 309bool ReorderProgramForSchurTypeLinearSolver( 310 const LinearSolverType linear_solver_type, 311 const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type, 312 const ProblemImpl::ParameterMap& parameter_map, 313 ParameterBlockOrdering* parameter_block_ordering, 314 Program* program, 315 string* error) { 316 if (parameter_block_ordering->NumGroups() == 1) { 317 // If the user supplied an parameter_block_ordering with just one 318 // group, it is equivalent to the user supplying NULL as an 319 // parameter_block_ordering. Ceres is completely free to choose the 320 // parameter block ordering as it sees fit. For Schur type solvers, 321 // this means that the user wishes for Ceres to identify the 322 // e_blocks, which we do by computing a maximal independent set. 323 vector<ParameterBlock*> schur_ordering; 324 const int num_eliminate_blocks = 325 ComputeStableSchurOrdering(*program, &schur_ordering); 326 327 CHECK_EQ(schur_ordering.size(), program->NumParameterBlocks()) 328 << "Congratulations, you found a Ceres bug! Please report this error " 329 << "to the developers."; 330 331 // Update the parameter_block_ordering object. 332 for (int i = 0; i < schur_ordering.size(); ++i) { 333 double* parameter_block = schur_ordering[i]->mutable_user_state(); 334 const int group_id = (i < num_eliminate_blocks) ? 0 : 1; 335 parameter_block_ordering->AddElementToGroup(parameter_block, group_id); 336 } 337 338 // We could call ApplyOrdering but this is cheaper and 339 // simpler. 340 swap(*program->mutable_parameter_blocks(), schur_ordering); 341 } else { 342 // The user provided an ordering with more than one elimination 343 // group. Trust the user and apply the ordering. 344 if (!ApplyOrdering(parameter_map, 345 *parameter_block_ordering, 346 program, 347 error)) { 348 return false; 349 } 350 } 351 352 if (linear_solver_type == SPARSE_SCHUR && 353 sparse_linear_algebra_library_type == SUITE_SPARSE) { 354 MaybeReorderSchurComplementColumnsUsingSuiteSparse( 355 *parameter_block_ordering, 356 program); 357 } 358 359 program->SetParameterOffsetsAndIndex(); 360 // Schur type solvers also require that their residual blocks be 361 // lexicographically ordered. 362 const int num_eliminate_blocks = 363 parameter_block_ordering->group_to_elements().begin()->second.size(); 364 if (!LexicographicallyOrderResidualBlocks(num_eliminate_blocks, 365 program, 366 error)) { 367 return false; 368 } 369 370 program->SetParameterOffsetsAndIndex(); 371 return true; 372} 373 374bool ReorderProgramForSparseNormalCholesky( 375 const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type, 376 const ParameterBlockOrdering& parameter_block_ordering, 377 Program* program, 378 string* error) { 379 380 if (sparse_linear_algebra_library_type != SUITE_SPARSE && 381 sparse_linear_algebra_library_type != CX_SPARSE && 382 sparse_linear_algebra_library_type != EIGEN_SPARSE) { 383 *error = "Unknown sparse linear algebra library."; 384 return false; 385 } 386 387 // For Eigen, there is nothing to do. This is because Eigen in its 388 // current stable version does not expose a method for doing 389 // symbolic analysis on pre-ordered matrices, so a block 390 // pre-ordering is a bit pointless. 391 // 392 // The dev version as recently as July 20, 2014 has support for 393 // pre-ordering. Once this becomes more widespread, or we add 394 // support for detecting Eigen versions, we can add support for this 395 // along the lines of CXSparse. 396 if (sparse_linear_algebra_library_type == EIGEN_SPARSE) { 397 program->SetParameterOffsetsAndIndex(); 398 return true; 399 } 400 401 // Set the offsets and index for CreateJacobianSparsityTranspose. 402 program->SetParameterOffsetsAndIndex(); 403 // Compute a block sparse presentation of J'. 404 scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose( 405 program->CreateJacobianBlockSparsityTranspose()); 406 407 vector<int> ordering(program->NumParameterBlocks(), 0); 408 vector<ParameterBlock*>& parameter_blocks = 409 *(program->mutable_parameter_blocks()); 410 411 if (sparse_linear_algebra_library_type == SUITE_SPARSE) { 412 OrderingForSparseNormalCholeskyUsingSuiteSparse( 413 *tsm_block_jacobian_transpose, 414 parameter_blocks, 415 parameter_block_ordering, 416 &ordering[0]); 417 } else if (sparse_linear_algebra_library_type == CX_SPARSE){ 418 OrderingForSparseNormalCholeskyUsingCXSparse( 419 *tsm_block_jacobian_transpose, 420 &ordering[0]); 421 } 422 423 // Apply ordering. 424 const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks); 425 for (int i = 0; i < program->NumParameterBlocks(); ++i) { 426 parameter_blocks[i] = parameter_blocks_copy[ordering[i]]; 427 } 428 429 program->SetParameterOffsetsAndIndex(); 430 return true; 431} 432 433} // namespace internal 434} // namespace ceres 435