Add the ability to order the Program using AMD.
This will allow CHOLMOD to compute the sparse
Cholesky factorization of J'J without making
a permuted copy of it.
Change-Id: I25d0e18f5957ab7fdce15c543234bb2f09db482e
diff --git a/internal/ceres/solver_impl.cc b/internal/ceres/solver_impl.cc
index add28ae..0d7b83c 100644
--- a/internal/ceres/solver_impl.cc
+++ b/internal/ceres/solver_impl.cc
@@ -50,6 +50,7 @@
#include "ceres/program.h"
#include "ceres/residual_block.h"
#include "ceres/stringprintf.h"
+#include "ceres/suitesparse.h"
#include "ceres/trust_region_minimizer.h"
#include "ceres/wall_time.h"
@@ -1003,6 +1004,11 @@
return transformed_program.release();
}
+ if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY) {
+ ReorderProgramForSparseNormalCholesky(transformed_program.get());
+ return transformed_program.release();
+ }
+
transformed_program->SetParameterOffsetsAndIndex();
return transformed_program.release();
}
@@ -1416,5 +1422,81 @@
error);
}
+TripletSparseMatrix* SolverImpl::CreateJacobianBlockSparsityTranspose(
+ const Program* program) {
+
+ // Matrix to store the block sparsity structure of
+ TripletSparseMatrix* tsm =
+ new TripletSparseMatrix(program->NumParameterBlocks(),
+ program->NumResidualBlocks(),
+ 10 * program->NumResidualBlocks());
+ int num_nonzeros = 0;
+ int* rows = tsm->mutable_rows();
+ int* cols = tsm->mutable_cols();
+ double* values = tsm->mutable_values();
+
+ const vector<ResidualBlock*>& residual_blocks = program->residual_blocks();
+ for (int c = 0; c < residual_blocks.size(); ++c) {
+ const ResidualBlock* residual_block = residual_blocks[c];
+ const int num_parameter_blocks = residual_block->NumParameterBlocks();
+ ParameterBlock* const* parameter_blocks =
+ residual_block->parameter_blocks();
+
+ for (int j = 0; j < num_parameter_blocks; ++j) {
+ if (parameter_blocks[j]->IsConstant()) {
+ continue;
+ }
+
+ // Re-size the matrix if needed.
+ if (num_nonzeros >= tsm->max_num_nonzeros()) {
+ tsm->Reserve(2 * num_nonzeros);
+ rows = tsm->mutable_rows();
+ cols = tsm->mutable_cols();
+ values = tsm->mutable_values();
+ }
+ CHECK_LT(num_nonzeros, tsm->max_num_nonzeros());
+
+ const int r = parameter_blocks[j]->index();
+ rows[num_nonzeros] = r;
+ cols[num_nonzeros] = c;
+ values[num_nonzeros] = 1.0;
+ ++num_nonzeros;
+ }
+ }
+
+ tsm->set_num_nonzeros(num_nonzeros);
+ return tsm;
+}
+
+void SolverImpl::ReorderProgramForSparseNormalCholesky(Program* program) {
+#ifndef CERES_NO_SUITESPARSE
+ // Set the offsets and index for CreateJacobianSparsityTranspose.
+ program->SetParameterOffsetsAndIndex();
+
+ // Compute a block sparse presentation of J'.
+ scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose(
+ SolverImpl::CreateJacobianBlockSparsityTranspose(program));
+
+ // Order rows using AMD.
+ SuiteSparse ss;
+ cholmod_sparse* block_jacobian_transpose =
+ ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get());
+
+ vector<int> ordering(program->NumResidualBlocks(), -1);
+ ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]);
+ ss.Free(block_jacobian_transpose);
+
+ // Apply ordering.
+ vector<ParameterBlock*>& parameter_blocks =
+ *(program->mutable_parameter_blocks());
+ const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks);
+ for (int i = 0; i < program->NumParameterBlocks(); ++i) {
+ parameter_blocks[i] = parameter_blocks_copy[ordering[i]];
+ }
+#endif
+
+ program->SetParameterOffsetsAndIndex();
+}
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/solver_impl.h b/internal/ceres/solver_impl.h
index 6377e84..22ca622 100644
--- a/internal/ceres/solver_impl.h
+++ b/internal/ceres/solver_impl.h
@@ -46,6 +46,7 @@
class Evaluator;
class LinearSolver;
class Program;
+class TripletSparseMatrix;
class SolverImpl {
public:
@@ -177,6 +178,21 @@
ParameterBlockOrdering* ordering,
Program* program,
string* error);
+
+ // CHOLMOD when doing the sparse cholesky factorization of the
+ // Jacobian matrix, reorders its columns to reduce the
+ // fill-in. Compute this permutation and re-order the parameter
+ // blocks.
+ //
+ static void ReorderProgramForSparseNormalCholesky(Program* program);
+
+ // Create a TripletSparseMatrix which contains the zero-one
+ // structure corresponding to the block sparsity of the transpose of
+ // the Jacobian matrix.
+ //
+ // Caller owns the result.
+ static TripletSparseMatrix* CreateJacobianBlockSparsityTranspose(
+ const Program* program);
};
} // namespace internal
diff --git a/internal/ceres/suitesparse.cc b/internal/ceres/suitesparse.cc
index 9063fa0..5138b52 100644
--- a/internal/ceres/suitesparse.cc
+++ b/internal/ceres/suitesparse.cc
@@ -397,6 +397,11 @@
return NULL;
}
+void SuiteSparse::ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix,
+ int* ordering) {
+ cholmod_amd(matrix, NULL, 0, ordering, &cc_);
+}
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/suitesparse.h b/internal/ceres/suitesparse.h
index 131716d..a1a4f35 100644
--- a/internal/ceres/suitesparse.h
+++ b/internal/ceres/suitesparse.h
@@ -207,6 +207,11 @@
vector<int>* block_rows,
vector<int>* block_cols);
+ // Find a fill reducing approximate minimum degree
+ // ordering. ordering is expected to be large enough to hold the
+ // ordering.
+ void ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix, int* ordering);
+
void Free(cholmod_sparse* m) { cholmod_free_sparse(&m, &cc_); }
void Free(cholmod_dense* m) { cholmod_free_dense(&m, &cc_); }
void Free(cholmod_factor* m) { cholmod_free_factor(&m, &cc_); }